13e0a4e85SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29a163ed8SThomas Gleixner /* -*- linux-c -*-
39a163ed8SThomas Gleixner * APM BIOS driver for Linux
49a163ed8SThomas Gleixner * Copyright 1994-2001 Stephen Rothwell (sfr@canb.auug.org.au)
59a163ed8SThomas Gleixner *
69a163ed8SThomas Gleixner * Initial development of this driver was funded by NEC Australia P/L
79a163ed8SThomas Gleixner * and NEC Corporation
89a163ed8SThomas Gleixner *
99a163ed8SThomas Gleixner * October 1995, Rik Faith (faith@cs.unc.edu):
109a163ed8SThomas Gleixner * Minor enhancements and updates (to the patch set) for 1.3.x
119a163ed8SThomas Gleixner * Documentation
129a163ed8SThomas Gleixner * January 1996, Rik Faith (faith@cs.unc.edu):
139a163ed8SThomas Gleixner * Make /proc/apm easy to format (bump driver version)
149a163ed8SThomas Gleixner * March 1996, Rik Faith (faith@cs.unc.edu):
159a163ed8SThomas Gleixner * Prohibit APM BIOS calls unless apm_enabled.
169a163ed8SThomas Gleixner * (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
179a163ed8SThomas Gleixner * April 1996, Stephen Rothwell (sfr@canb.auug.org.au)
189a163ed8SThomas Gleixner * Version 1.0 and 1.1
199a163ed8SThomas Gleixner * May 1996, Version 1.2
209a163ed8SThomas Gleixner * Feb 1998, Version 1.3
219a163ed8SThomas Gleixner * Feb 1998, Version 1.4
229a163ed8SThomas Gleixner * Aug 1998, Version 1.5
239a163ed8SThomas Gleixner * Sep 1998, Version 1.6
249a163ed8SThomas Gleixner * Nov 1998, Version 1.7
259a163ed8SThomas Gleixner * Jan 1999, Version 1.8
269a163ed8SThomas Gleixner * Jan 1999, Version 1.9
279a163ed8SThomas Gleixner * Oct 1999, Version 1.10
289a163ed8SThomas Gleixner * Nov 1999, Version 1.11
299a163ed8SThomas Gleixner * Jan 2000, Version 1.12
309a163ed8SThomas Gleixner * Feb 2000, Version 1.13
319a163ed8SThomas Gleixner * Nov 2000, Version 1.14
329a163ed8SThomas Gleixner * Oct 2001, Version 1.15
339a163ed8SThomas Gleixner * Jan 2002, Version 1.16
349a163ed8SThomas Gleixner * Oct 2002, Version 1.16ac
359a163ed8SThomas Gleixner *
369a163ed8SThomas Gleixner * History:
379a163ed8SThomas Gleixner * 0.6b: first version in official kernel, Linux 1.3.46
389a163ed8SThomas Gleixner * 0.7: changed /proc/apm format, Linux 1.3.58
399a163ed8SThomas Gleixner * 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
409a163ed8SThomas Gleixner * 0.9: only call bios if bios is present, Linux 1.3.72
419a163ed8SThomas Gleixner * 1.0: use fixed device number, consolidate /proc/apm into this file,
429a163ed8SThomas Gleixner * Linux 1.3.85
439a163ed8SThomas Gleixner * 1.1: support user-space standby and suspend, power off after system
449a163ed8SThomas Gleixner * halted, Linux 1.3.98
459a163ed8SThomas Gleixner * 1.2: When resetting RTC after resume, take care so that the time
469a163ed8SThomas Gleixner * is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth
479a163ed8SThomas Gleixner * <jtoth@princeton.edu>); improve interaction between
489a163ed8SThomas Gleixner * screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4
499a163ed8SThomas Gleixner * 1.2a:Simple change to stop mysterious bug reports with SMP also added
509a163ed8SThomas Gleixner * levels to the printk calls. APM is not defined for SMP machines.
5127b46d76SSimon Arlott * The new replacement for it is, but Linux doesn't yet support this.
529a163ed8SThomas Gleixner * Alan Cox Linux 2.1.55
539a163ed8SThomas Gleixner * 1.3: Set up a valid data descriptor 0x40 for buggy BIOS's
549a163ed8SThomas Gleixner * 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by
559a163ed8SThomas Gleixner * Dean Gaudet <dgaudet@arctic.org>.
569a163ed8SThomas Gleixner * C. Scott Ananian <cananian@alumni.princeton.edu> Linux 2.1.87
579a163ed8SThomas Gleixner * 1.5: Fix segment register reloading (in case of bad segments saved
589a163ed8SThomas Gleixner * across BIOS call).
599a163ed8SThomas Gleixner * Stephen Rothwell
600d2eb44fSLucas De Marchi * 1.6: Cope with compiler/assembler differences.
619a163ed8SThomas Gleixner * Only try to turn off the first display device.
629a163ed8SThomas Gleixner * Fix OOPS at power off with no APM BIOS by Jan Echternach
639a163ed8SThomas Gleixner * <echter@informatik.uni-rostock.de>
649a163ed8SThomas Gleixner * Stephen Rothwell
659a163ed8SThomas Gleixner * 1.7: Modify driver's cached copy of the disabled/disengaged flags
669a163ed8SThomas Gleixner * to reflect current state of APM BIOS.
679a163ed8SThomas Gleixner * Chris Rankin <rankinc@bellsouth.net>
689a163ed8SThomas Gleixner * Reset interrupt 0 timer to 100Hz after suspend
699a163ed8SThomas Gleixner * Chad Miller <cmiller@surfsouth.com>
709a163ed8SThomas Gleixner * Add CONFIG_APM_IGNORE_SUSPEND_BOUNCE
719a163ed8SThomas Gleixner * Richard Gooch <rgooch@atnf.csiro.au>
729a163ed8SThomas Gleixner * Allow boot time disabling of APM
739a163ed8SThomas Gleixner * Make boot messages far less verbose by default
749a163ed8SThomas Gleixner * Make asm safer
759a163ed8SThomas Gleixner * Stephen Rothwell
769a163ed8SThomas Gleixner * 1.8: Add CONFIG_APM_RTC_IS_GMT
779a163ed8SThomas Gleixner * Richard Gooch <rgooch@atnf.csiro.au>
789a163ed8SThomas Gleixner * change APM_NOINTS to CONFIG_APM_ALLOW_INTS
799a163ed8SThomas Gleixner * remove dependency on CONFIG_PROC_FS
809a163ed8SThomas Gleixner * Stephen Rothwell
819a163ed8SThomas Gleixner * 1.9: Fix small typo. <laslo@wodip.opole.pl>
829a163ed8SThomas Gleixner * Try to cope with BIOS's that need to have all display
839a163ed8SThomas Gleixner * devices blanked and not just the first one.
849a163ed8SThomas Gleixner * Ross Paterson <ross@soi.city.ac.uk>
859a163ed8SThomas Gleixner * Fix segment limit setting it has always been wrong as
869a163ed8SThomas Gleixner * the segments needed to have byte granularity.
879a163ed8SThomas Gleixner * Mark a few things __init.
889a163ed8SThomas Gleixner * Add hack to allow power off of SMP systems by popular request.
899a163ed8SThomas Gleixner * Use CONFIG_SMP instead of __SMP__
909a163ed8SThomas Gleixner * Ignore BOUNCES for three seconds.
919a163ed8SThomas Gleixner * Stephen Rothwell
929a163ed8SThomas Gleixner * 1.10: Fix for Thinkpad return code.
939a163ed8SThomas Gleixner * Merge 2.2 and 2.3 drivers.
949a163ed8SThomas Gleixner * Remove APM dependencies in arch/i386/kernel/process.c
959a163ed8SThomas Gleixner * Remove APM dependencies in drivers/char/sysrq.c
969a163ed8SThomas Gleixner * Reset time across standby.
97d9f6e12fSIngo Molnar * Allow more initialisation on SMP.
989a163ed8SThomas Gleixner * Remove CONFIG_APM_POWER_OFF and make it boot time
999a163ed8SThomas Gleixner * configurable (default on).
1009a163ed8SThomas Gleixner * Make debug only a boot time parameter (remove APM_DEBUG).
1019a163ed8SThomas Gleixner * Try to blank all devices on any error.
1029a163ed8SThomas Gleixner * 1.11: Remove APM dependencies in drivers/char/console.c
1039a163ed8SThomas Gleixner * Check nr_running to detect if we are idle (from
1049a163ed8SThomas Gleixner * Borislav Deianov <borislav@lix.polytechnique.fr>)
1059a163ed8SThomas Gleixner * Fix for bioses that don't zero the top part of the
1069a163ed8SThomas Gleixner * entrypoint offset (Mario Sitta <sitta@al.unipmn.it>)
1079a163ed8SThomas Gleixner * (reported by Panos Katsaloulis <teras@writeme.com>).
1089a163ed8SThomas Gleixner * Real mode power off patch (Walter Hofmann
1099a163ed8SThomas Gleixner * <Walter.Hofmann@physik.stud.uni-erlangen.de>).
1109a163ed8SThomas Gleixner * 1.12: Remove CONFIG_SMP as the compiler will optimize
1119a163ed8SThomas Gleixner * the code away anyway (smp_num_cpus == 1 in UP)
1129a163ed8SThomas Gleixner * noted by Artur Skawina <skawina@geocities.com>.
1139a163ed8SThomas Gleixner * Make power off under SMP work again.
1149a163ed8SThomas Gleixner * Fix thinko with initial engaging of BIOS.
1159a163ed8SThomas Gleixner * Make sure power off only happens on CPU 0
1169a163ed8SThomas Gleixner * (Paul "Rusty" Russell <rusty@rustcorp.com.au>).
1179a163ed8SThomas Gleixner * Do error notification to user mode if BIOS calls fail.
1189a163ed8SThomas Gleixner * Move entrypoint offset fix to ...boot/setup.S
1199a163ed8SThomas Gleixner * where it belongs (Cosmos <gis88564@cis.nctu.edu.tw>).
1209a163ed8SThomas Gleixner * Remove smp-power-off. SMP users must now specify
1219a163ed8SThomas Gleixner * "apm=power-off" on the kernel command line. Suggested
1229a163ed8SThomas Gleixner * by Jim Avera <jima@hal.com>, modified by Alan Cox
1239a163ed8SThomas Gleixner * <alan@lxorguk.ukuu.org.uk>.
1249a163ed8SThomas Gleixner * Register the /proc/apm entry even on SMP so that
1259a163ed8SThomas Gleixner * scripts that check for it before doing power off
1269a163ed8SThomas Gleixner * work (Jim Avera <jima@hal.com>).
1279a163ed8SThomas Gleixner * 1.13: Changes for new pm_ interfaces (Andy Henroid
1289a163ed8SThomas Gleixner * <andy_henroid@yahoo.com>).
1299a163ed8SThomas Gleixner * Modularize the code.
1309a163ed8SThomas Gleixner * Fix the Thinkpad (again) :-( (CONFIG_APM_IGNORE_MULTIPLE_SUSPENDS
1319a163ed8SThomas Gleixner * is now the way life works).
1329a163ed8SThomas Gleixner * Fix thinko in suspend() (wrong return).
1339a163ed8SThomas Gleixner * Notify drivers on critical suspend.
134a2531293SPavel Machek * Make kapmd absorb more idle time (Pavel Machek <pavel@ucw.cz>
1359a163ed8SThomas Gleixner * modified by sfr).
1369a163ed8SThomas Gleixner * Disable interrupts while we are suspended (Andy Henroid
1379a163ed8SThomas Gleixner * <andy_henroid@yahoo.com> fixed by sfr).
1389a163ed8SThomas Gleixner * Make power off work on SMP again (Tony Hoyle
1399a163ed8SThomas Gleixner * <tmh@magenta-logic.com> and <zlatko@iskon.hr>) modified by sfr.
1409a163ed8SThomas Gleixner * Remove CONFIG_APM_SUSPEND_BOUNCE. The bounce ignore
1419a163ed8SThomas Gleixner * interval is now configurable.
1429a163ed8SThomas Gleixner * 1.14: Make connection version persist across module unload/load.
1439a163ed8SThomas Gleixner * Enable and engage power management earlier.
1449a163ed8SThomas Gleixner * Disengage power management on module unload.
1459a163ed8SThomas Gleixner * Changed to use the sysrq-register hack for registering the
1469a163ed8SThomas Gleixner * power off function called by magic sysrq based upon discussions
1479a163ed8SThomas Gleixner * in irc://irc.openprojects.net/#kernelnewbies
1489a163ed8SThomas Gleixner * (Crutcher Dunnavant <crutcher+kernel@datastacks.com>).
1499a163ed8SThomas Gleixner * Make CONFIG_APM_REAL_MODE_POWER_OFF run time configurable.
1509a163ed8SThomas Gleixner * (Arjan van de Ven <arjanv@redhat.com>) modified by sfr.
1519a163ed8SThomas Gleixner * Work around byte swap bug in one of the Vaio's BIOS's
1529a163ed8SThomas Gleixner * (Marc Boucher <marc@mbsi.ca>).
1539a163ed8SThomas Gleixner * Exposed the disable flag to dmi so that we can handle known
15487c6fe26SAlan Cox * broken APM (Alan Cox <alan@lxorguk.ukuu.org.uk>).
1559a163ed8SThomas Gleixner * 1.14ac: If the BIOS says "I slowed the CPU down" then don't spin
15687c6fe26SAlan Cox * calling it - instead idle. (Alan Cox <alan@lxorguk.ukuu.org.uk>)
1579a163ed8SThomas Gleixner * If an APM idle fails log it and idle sensibly
1589a163ed8SThomas Gleixner * 1.15: Don't queue events to clients who open the device O_WRONLY.
1599a163ed8SThomas Gleixner * Don't expect replies from clients who open the device O_RDONLY.
1609a163ed8SThomas Gleixner * (Idea from Thomas Hood)
1619a163ed8SThomas Gleixner * Minor waitqueue cleanups. (John Fremlin <chief@bandits.org>)
1629a163ed8SThomas Gleixner * 1.16: Fix idle calling. (Andreas Steinmetz <ast@domdv.de> et al.)
1639a163ed8SThomas Gleixner * Notify listeners of standby or suspend events before notifying
1649a163ed8SThomas Gleixner * drivers. Return EBUSY to ioctl() if suspend is rejected.
1659a163ed8SThomas Gleixner * (Russell King <rmk@arm.linux.org.uk> and Thomas Hood)
1669a163ed8SThomas Gleixner * Ignore first resume after we generate our own resume event
1679a163ed8SThomas Gleixner * after a suspend (Thomas Hood)
1689a163ed8SThomas Gleixner * Daemonize now gets rid of our controlling terminal (sfr).
1699a163ed8SThomas Gleixner * CONFIG_APM_CPU_IDLE now just affects the default value of
1709a163ed8SThomas Gleixner * idle_threshold (sfr).
1719a163ed8SThomas Gleixner * Change name of kernel apm daemon (as it no longer idles) (sfr).
1729a163ed8SThomas Gleixner * 1.16ac: Fix up SMP support somewhat. You can now force SMP on and we
1739a163ed8SThomas Gleixner * make _all_ APM calls on the CPU#0. Fix unsafe sign bug.
1749a163ed8SThomas Gleixner * TODO: determine if its "boot CPU" or "CPU0" we want to lock to.
1759a163ed8SThomas Gleixner *
1769a163ed8SThomas Gleixner * APM 1.1 Reference:
1779a163ed8SThomas Gleixner *
1789a163ed8SThomas Gleixner * Intel Corporation, Microsoft Corporation. Advanced Power Management
1799a163ed8SThomas Gleixner * (APM) BIOS Interface Specification, Revision 1.1, September 1993.
1809a163ed8SThomas Gleixner * Intel Order Number 241704-001. Microsoft Part Number 781-110-X01.
1819a163ed8SThomas Gleixner *
1829a163ed8SThomas Gleixner * [This document is available free from Intel by calling 800.628.8686 (fax
18350a23e6eSJustin P. Mattock * 916.356.6100) or 800.548.4725; or from
18450a23e6eSJustin P. Mattock * http://www.microsoft.com/whdc/archive/amp_12.mspx It is also
1859a163ed8SThomas Gleixner * available from Microsoft by calling 206.882.8080.]
1869a163ed8SThomas Gleixner *
1879a163ed8SThomas Gleixner * APM 1.2 Reference:
1889a163ed8SThomas Gleixner * Intel Corporation, Microsoft Corporation. Advanced Power Management
1899a163ed8SThomas Gleixner * (APM) BIOS Interface Specification, Revision 1.2, February 1996.
1909a163ed8SThomas Gleixner *
1919a163ed8SThomas Gleixner * [This document is available from Microsoft at:
1929a163ed8SThomas Gleixner * http://www.microsoft.com/whdc/archive/amp_12.mspx]
1939a163ed8SThomas Gleixner */
1949a163ed8SThomas Gleixner
195c767a54bSJoe Perches #define pr_fmt(fmt) "apm: " fmt
196c767a54bSJoe Perches
1979a163ed8SThomas Gleixner #include <linux/module.h>
1989a163ed8SThomas Gleixner
1999a163ed8SThomas Gleixner #include <linux/poll.h>
2009a163ed8SThomas Gleixner #include <linux/types.h>
2019a163ed8SThomas Gleixner #include <linux/stddef.h>
2029a163ed8SThomas Gleixner #include <linux/timer.h>
2039a163ed8SThomas Gleixner #include <linux/fcntl.h>
2049a163ed8SThomas Gleixner #include <linux/slab.h>
2059a163ed8SThomas Gleixner #include <linux/stat.h>
2069a163ed8SThomas Gleixner #include <linux/proc_fs.h>
2079a163ed8SThomas Gleixner #include <linux/seq_file.h>
2089a163ed8SThomas Gleixner #include <linux/miscdevice.h>
2099a163ed8SThomas Gleixner #include <linux/apm_bios.h>
2109a163ed8SThomas Gleixner #include <linux/init.h>
2119a163ed8SThomas Gleixner #include <linux/time.h>
212174cd4b1SIngo Molnar #include <linux/sched/signal.h>
21332ef5517SIngo Molnar #include <linux/sched/cputime.h>
2149a163ed8SThomas Gleixner #include <linux/pm.h>
2159a163ed8SThomas Gleixner #include <linux/capability.h>
2169a163ed8SThomas Gleixner #include <linux/device.h>
2179a163ed8SThomas Gleixner #include <linux/kernel.h>
2189a163ed8SThomas Gleixner #include <linux/freezer.h>
2199a163ed8SThomas Gleixner #include <linux/smp.h>
2209a163ed8SThomas Gleixner #include <linux/dmi.h>
2219a163ed8SThomas Gleixner #include <linux/suspend.h>
2229a163ed8SThomas Gleixner #include <linux/kthread.h>
2234f2479f0SJulia Lawall #include <linux/jiffies.h>
2246831c6edSRafael J. Wysocki #include <linux/acpi.h>
22519234c08SRafael J. Wysocki #include <linux/syscore_ops.h>
226334955efSRalf Baechle #include <linux/i8253.h>
227dd8af076SLen Brown #include <linux/cpuidle.h>
2289a163ed8SThomas Gleixner
2297c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
2309a163ed8SThomas Gleixner #include <asm/desc.h>
23177a9a768SJeremy Katz #include <asm/olpc.h>
2329a163ed8SThomas Gleixner #include <asm/paravirt.h>
2339a163ed8SThomas Gleixner #include <asm/reboot.h>
2346f6060a5SVille Syrjälä #include <asm/nospec-branch.h>
235fe379fa4SPeter Zijlstra #include <asm/ibt.h>
2369a163ed8SThomas Gleixner
2379a163ed8SThomas Gleixner #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
2389a163ed8SThomas Gleixner extern int (*console_blank_hook)(int);
2399a163ed8SThomas Gleixner #endif
2409a163ed8SThomas Gleixner
2419a163ed8SThomas Gleixner /*
2429a163ed8SThomas Gleixner * Various options can be changed at boot time as follows:
2439a163ed8SThomas Gleixner * (We allow underscores for compatibility with the modules code)
2449a163ed8SThomas Gleixner * apm=on/off enable/disable APM
2459a163ed8SThomas Gleixner * [no-]allow[-_]ints allow interrupts during BIOS calls
2469a163ed8SThomas Gleixner * [no-]broken[-_]psr BIOS has a broken GetPowerStatus call
2479a163ed8SThomas Gleixner * [no-]realmode[-_]power[-_]off switch to real mode before
2489a163ed8SThomas Gleixner * powering off
2499a163ed8SThomas Gleixner * [no-]debug log some debugging messages
2509a163ed8SThomas Gleixner * [no-]power[-_]off power off on shutdown
2519a163ed8SThomas Gleixner * [no-]smp Use apm even on an SMP box
2529a163ed8SThomas Gleixner * bounce[-_]interval=<n> number of ticks to ignore suspend
2539a163ed8SThomas Gleixner * bounces
2549a163ed8SThomas Gleixner * idle[-_]threshold=<n> System idle percentage above which to
2559a163ed8SThomas Gleixner * make APM BIOS idle calls. Set it to
2569a163ed8SThomas Gleixner * 100 to disable.
2579a163ed8SThomas Gleixner * idle[-_]period=<n> Period (in 1/100s of a second) over
2589a163ed8SThomas Gleixner * which the idle percentage is
2599a163ed8SThomas Gleixner * calculated.
2609a163ed8SThomas Gleixner */
2619a163ed8SThomas Gleixner
2629a163ed8SThomas Gleixner /* KNOWN PROBLEM MACHINES:
2639a163ed8SThomas Gleixner *
2649a163ed8SThomas Gleixner * U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
2659a163ed8SThomas Gleixner * [Confirmed by TI representative]
2669a163ed8SThomas Gleixner * ?: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
2679a163ed8SThomas Gleixner * [Confirmed by BIOS disassembly]
2689a163ed8SThomas Gleixner * [This may work now ...]
2699a163ed8SThomas Gleixner * P: Toshiba 1950S: battery life information only gets updated after resume
2709a163ed8SThomas Gleixner * P: Midwest Micro Soundbook Elite DX2/66 monochrome: screen blanking
2719a163ed8SThomas Gleixner * broken in BIOS [Reported by Garst R. Reese <reese@isn.net>]
2729a163ed8SThomas Gleixner * ?: AcerNote-950: oops on reading /proc/apm - workaround is a WIP
2739a163ed8SThomas Gleixner * Neale Banks <neale@lowendale.com.au> December 2000
2749a163ed8SThomas Gleixner *
2759a163ed8SThomas Gleixner * Legend: U = unusable with APM patches
2769a163ed8SThomas Gleixner * P = partially usable with APM patches
2779a163ed8SThomas Gleixner */
2789a163ed8SThomas Gleixner
2799a163ed8SThomas Gleixner /*
2809a163ed8SThomas Gleixner * Define as 1 to make the driver always call the APM BIOS busy
2819a163ed8SThomas Gleixner * routine even if the clock was not reported as slowed by the
2829a163ed8SThomas Gleixner * idle routine. Otherwise, define as 0.
2839a163ed8SThomas Gleixner */
2849a163ed8SThomas Gleixner #define ALWAYS_CALL_BUSY 1
2859a163ed8SThomas Gleixner
2869a163ed8SThomas Gleixner /*
2879a163ed8SThomas Gleixner * Define to make the APM BIOS calls zero all data segment registers (so
2889a163ed8SThomas Gleixner * that an incorrect BIOS implementation will cause a kernel panic if it
2899a163ed8SThomas Gleixner * tries to write to arbitrary memory).
2909a163ed8SThomas Gleixner */
2919a163ed8SThomas Gleixner #define APM_ZERO_SEGS
2929a163ed8SThomas Gleixner
2931164dd00SIngo Molnar #include <asm/apm.h>
2949a163ed8SThomas Gleixner
2959a163ed8SThomas Gleixner /*
2969a163ed8SThomas Gleixner * Define to re-initialize the interrupt 0 timer to 100 Hz after a suspend.
2979a163ed8SThomas Gleixner * This patched by Chad Miller <cmiller@surfsouth.com>, original code by
2989a163ed8SThomas Gleixner * David Chen <chen@ctpa04.mit.edu>
2999a163ed8SThomas Gleixner */
3009a163ed8SThomas Gleixner #undef INIT_TIMER_AFTER_SUSPEND
3019a163ed8SThomas Gleixner
3029a163ed8SThomas Gleixner #ifdef INIT_TIMER_AFTER_SUSPEND
3039a163ed8SThomas Gleixner #include <linux/timex.h>
3049a163ed8SThomas Gleixner #include <asm/io.h>
3059a163ed8SThomas Gleixner #include <linux/delay.h>
3069a163ed8SThomas Gleixner #endif
3079a163ed8SThomas Gleixner
3089a163ed8SThomas Gleixner /*
3099a163ed8SThomas Gleixner * Need to poll the APM BIOS every second
3109a163ed8SThomas Gleixner */
3119a163ed8SThomas Gleixner #define APM_CHECK_TIMEOUT (HZ)
3129a163ed8SThomas Gleixner
3139a163ed8SThomas Gleixner /*
3149a163ed8SThomas Gleixner * Ignore suspend events for this amount of time after a resume
3159a163ed8SThomas Gleixner */
3169a163ed8SThomas Gleixner #define DEFAULT_BOUNCE_INTERVAL (3 * HZ)
3179a163ed8SThomas Gleixner
3189a163ed8SThomas Gleixner /*
3199a163ed8SThomas Gleixner * Maximum number of events stored
3209a163ed8SThomas Gleixner */
3219a163ed8SThomas Gleixner #define APM_MAX_EVENTS 20
3229a163ed8SThomas Gleixner
3239a163ed8SThomas Gleixner /*
3249a163ed8SThomas Gleixner * The per-file APM data
3259a163ed8SThomas Gleixner */
3269a163ed8SThomas Gleixner struct apm_user {
3279a163ed8SThomas Gleixner int magic;
3289a163ed8SThomas Gleixner struct apm_user *next;
3299a163ed8SThomas Gleixner unsigned int suser: 1;
3309a163ed8SThomas Gleixner unsigned int writer: 1;
3319a163ed8SThomas Gleixner unsigned int reader: 1;
3329a163ed8SThomas Gleixner unsigned int suspend_wait: 1;
3339a163ed8SThomas Gleixner int suspend_result;
3349a163ed8SThomas Gleixner int suspends_pending;
3359a163ed8SThomas Gleixner int standbys_pending;
3369a163ed8SThomas Gleixner int suspends_read;
3379a163ed8SThomas Gleixner int standbys_read;
3389a163ed8SThomas Gleixner int event_head;
3399a163ed8SThomas Gleixner int event_tail;
3409a163ed8SThomas Gleixner apm_event_t events[APM_MAX_EVENTS];
3419a163ed8SThomas Gleixner };
3429a163ed8SThomas Gleixner
3439a163ed8SThomas Gleixner /*
3449a163ed8SThomas Gleixner * The magic number in apm_user
3459a163ed8SThomas Gleixner */
3469a163ed8SThomas Gleixner #define APM_BIOS_MAGIC 0x4101
3479a163ed8SThomas Gleixner
3489a163ed8SThomas Gleixner /*
3499a163ed8SThomas Gleixner * idle percentage above which bios idle calls are done
3509a163ed8SThomas Gleixner */
3519a163ed8SThomas Gleixner #ifdef CONFIG_APM_CPU_IDLE
3529a163ed8SThomas Gleixner #define DEFAULT_IDLE_THRESHOLD 95
3539a163ed8SThomas Gleixner #else
3549a163ed8SThomas Gleixner #define DEFAULT_IDLE_THRESHOLD 100
3559a163ed8SThomas Gleixner #endif
3569a163ed8SThomas Gleixner #define DEFAULT_IDLE_PERIOD (100 / 3)
3579a163ed8SThomas Gleixner
358dd8af076SLen Brown static int apm_cpu_idle(struct cpuidle_device *dev,
359dd8af076SLen Brown struct cpuidle_driver *drv, int index);
360dd8af076SLen Brown
361dd8af076SLen Brown static struct cpuidle_driver apm_idle_driver = {
362dd8af076SLen Brown .name = "apm_idle",
363dd8af076SLen Brown .owner = THIS_MODULE,
364dd8af076SLen Brown .states = {
365dd8af076SLen Brown { /* entry 0 is for polling */ },
366dd8af076SLen Brown { /* entry 1 is for APM idle */
367dd8af076SLen Brown .name = "APM",
368dd8af076SLen Brown .desc = "APM idle",
369dd8af076SLen Brown .exit_latency = 250, /* WAG */
370dd8af076SLen Brown .target_residency = 500, /* WAG */
371dd8af076SLen Brown .enter = &apm_cpu_idle
372dd8af076SLen Brown },
373dd8af076SLen Brown },
374dd8af076SLen Brown .state_count = 2,
375dd8af076SLen Brown };
376dd8af076SLen Brown
377dd8af076SLen Brown static struct cpuidle_device apm_cpuidle_device;
378dd8af076SLen Brown
3799a163ed8SThomas Gleixner /*
3809a163ed8SThomas Gleixner * Local variables
3819a163ed8SThomas Gleixner */
38254c2f3fdSAndi Kleen __visible struct {
3839a163ed8SThomas Gleixner unsigned long offset;
3849a163ed8SThomas Gleixner unsigned short segment;
3859a163ed8SThomas Gleixner } apm_bios_entry;
3869a163ed8SThomas Gleixner static int clock_slowed;
3879a163ed8SThomas Gleixner static int idle_threshold __read_mostly = DEFAULT_IDLE_THRESHOLD;
3889a163ed8SThomas Gleixner static int idle_period __read_mostly = DEFAULT_IDLE_PERIOD;
3899a163ed8SThomas Gleixner static int suspends_pending;
3909a163ed8SThomas Gleixner static int standbys_pending;
3919a163ed8SThomas Gleixner static int ignore_sys_suspend;
3929a163ed8SThomas Gleixner static int ignore_normal_resume;
3939a163ed8SThomas Gleixner static int bounce_interval __read_mostly = DEFAULT_BOUNCE_INTERVAL;
3949a163ed8SThomas Gleixner
395476bc001SRusty Russell static bool debug __read_mostly;
396476bc001SRusty Russell static bool smp __read_mostly;
3979a163ed8SThomas Gleixner static int apm_disabled = -1;
3989a163ed8SThomas Gleixner #ifdef CONFIG_SMP
399476bc001SRusty Russell static bool power_off;
4009a163ed8SThomas Gleixner #else
401476bc001SRusty Russell static bool power_off = 1;
4029a163ed8SThomas Gleixner #endif
403476bc001SRusty Russell static bool realmode_power_off;
4049a163ed8SThomas Gleixner #ifdef CONFIG_APM_ALLOW_INTS
405476bc001SRusty Russell static bool allow_ints = 1;
4069a163ed8SThomas Gleixner #else
407476bc001SRusty Russell static bool allow_ints;
4089a163ed8SThomas Gleixner #endif
409476bc001SRusty Russell static bool broken_psr;
4109a163ed8SThomas Gleixner
4119a163ed8SThomas Gleixner static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
4129a163ed8SThomas Gleixner static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
4139a163ed8SThomas Gleixner static struct apm_user *user_list;
4149a163ed8SThomas Gleixner static DEFINE_SPINLOCK(user_list_lock);
41505d86412SThomas Gleixner static DEFINE_MUTEX(apm_mutex);
416c7425314SAkinobu Mita
417c7425314SAkinobu Mita /*
418c7425314SAkinobu Mita * Set up a segment that references the real mode segment 0x40
419c7425314SAkinobu Mita * that extends up to the end of page zero (that we have reserved).
420c7425314SAkinobu Mita * This is for buggy BIOS's that refer to (real mode) segment 0x40
421c7425314SAkinobu Mita * even though they are called in protected mode.
422c7425314SAkinobu Mita */
423c7425314SAkinobu Mita static struct desc_struct bad_bios_desc = GDT_ENTRY_INIT(0x4092,
424c7425314SAkinobu Mita (unsigned long)__va(0x400UL), PAGE_SIZE - 0x400 - 1);
4259a163ed8SThomas Gleixner
4269a163ed8SThomas Gleixner static const char driver_version[] = "1.16ac"; /* no spaces */
4279a163ed8SThomas Gleixner
4289a163ed8SThomas Gleixner static struct task_struct *kapmd_task;
4299a163ed8SThomas Gleixner
4309a163ed8SThomas Gleixner /*
4319a163ed8SThomas Gleixner * APM event names taken from the APM 1.2 specification. These are
4329a163ed8SThomas Gleixner * the message codes that the BIOS uses to tell us about events
4339a163ed8SThomas Gleixner */
4349a163ed8SThomas Gleixner static const char * const apm_event_name[] = {
4359a163ed8SThomas Gleixner "system standby",
4369a163ed8SThomas Gleixner "system suspend",
4379a163ed8SThomas Gleixner "normal resume",
4389a163ed8SThomas Gleixner "critical resume",
4399a163ed8SThomas Gleixner "low battery",
4409a163ed8SThomas Gleixner "power status change",
4419a163ed8SThomas Gleixner "update time",
4429a163ed8SThomas Gleixner "critical suspend",
4439a163ed8SThomas Gleixner "user standby",
4449a163ed8SThomas Gleixner "user suspend",
4459a163ed8SThomas Gleixner "system standby resume",
4469a163ed8SThomas Gleixner "capabilities change"
4479a163ed8SThomas Gleixner };
4489a163ed8SThomas Gleixner #define NR_APM_EVENT_NAME ARRAY_SIZE(apm_event_name)
4499a163ed8SThomas Gleixner
4509a163ed8SThomas Gleixner typedef struct lookup_t {
4519a163ed8SThomas Gleixner int key;
4529a163ed8SThomas Gleixner char *msg;
4539a163ed8SThomas Gleixner } lookup_t;
4549a163ed8SThomas Gleixner
4559a163ed8SThomas Gleixner /*
4569a163ed8SThomas Gleixner * The BIOS returns a set of standard error codes in AX when the
4579a163ed8SThomas Gleixner * carry flag is set.
4589a163ed8SThomas Gleixner */
4599a163ed8SThomas Gleixner
4609a163ed8SThomas Gleixner static const lookup_t error_table[] = {
4619a163ed8SThomas Gleixner /* N/A { APM_SUCCESS, "Operation succeeded" }, */
4629a163ed8SThomas Gleixner { APM_DISABLED, "Power management disabled" },
4639a163ed8SThomas Gleixner { APM_CONNECTED, "Real mode interface already connected" },
4649a163ed8SThomas Gleixner { APM_NOT_CONNECTED, "Interface not connected" },
4659a163ed8SThomas Gleixner { APM_16_CONNECTED, "16 bit interface already connected" },
4669a163ed8SThomas Gleixner /* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
4679a163ed8SThomas Gleixner { APM_32_CONNECTED, "32 bit interface already connected" },
4689a163ed8SThomas Gleixner { APM_32_UNSUPPORTED, "32 bit interface not supported" },
4699a163ed8SThomas Gleixner { APM_BAD_DEVICE, "Unrecognized device ID" },
4709a163ed8SThomas Gleixner { APM_BAD_PARAM, "Parameter out of range" },
4719a163ed8SThomas Gleixner { APM_NOT_ENGAGED, "Interface not engaged" },
4729a163ed8SThomas Gleixner { APM_BAD_FUNCTION, "Function not supported" },
4739a163ed8SThomas Gleixner { APM_RESUME_DISABLED, "Resume timer disabled" },
4749a163ed8SThomas Gleixner { APM_BAD_STATE, "Unable to enter requested state" },
4759a163ed8SThomas Gleixner /* N/A { APM_NO_EVENTS, "No events pending" }, */
4769a163ed8SThomas Gleixner { APM_NO_ERROR, "BIOS did not set a return code" },
4779a163ed8SThomas Gleixner { APM_NOT_PRESENT, "No APM present" }
4789a163ed8SThomas Gleixner };
4799a163ed8SThomas Gleixner #define ERROR_COUNT ARRAY_SIZE(error_table)
4809a163ed8SThomas Gleixner
4819a163ed8SThomas Gleixner /**
4829a163ed8SThomas Gleixner * apm_error - display an APM error
4839a163ed8SThomas Gleixner * @str: information string
4849a163ed8SThomas Gleixner * @err: APM BIOS return code
4859a163ed8SThomas Gleixner *
4869a163ed8SThomas Gleixner * Write a meaningful log entry to the kernel log in the event of
48789bd55d1SRusty Russell * an APM error. Note that this also handles (negative) kernel errors.
4889a163ed8SThomas Gleixner */
4899a163ed8SThomas Gleixner
apm_error(char * str,int err)4909a163ed8SThomas Gleixner static void apm_error(char *str, int err)
4919a163ed8SThomas Gleixner {
4929a163ed8SThomas Gleixner int i;
4939a163ed8SThomas Gleixner
4949a163ed8SThomas Gleixner for (i = 0; i < ERROR_COUNT; i++)
4953f4380a1SCyrill Gorcunov if (error_table[i].key == err)
4963f4380a1SCyrill Gorcunov break;
4979a163ed8SThomas Gleixner if (i < ERROR_COUNT)
498c767a54bSJoe Perches pr_notice("%s: %s\n", str, error_table[i].msg);
49989bd55d1SRusty Russell else if (err < 0)
500c767a54bSJoe Perches pr_notice("%s: linux error code %i\n", str, err);
5019a163ed8SThomas Gleixner else
502c767a54bSJoe Perches pr_notice("%s: unknown error code %#2.2x\n",
5039a163ed8SThomas Gleixner str, err);
5049a163ed8SThomas Gleixner }
5059a163ed8SThomas Gleixner
5069a163ed8SThomas Gleixner /*
5079a163ed8SThomas Gleixner * These are the actual BIOS calls. Depending on APM_ZERO_SEGS and
5089a163ed8SThomas Gleixner * apm_info.allow_ints, we are being really paranoid here! Not only
5099a163ed8SThomas Gleixner * are interrupts disabled, but all the segment registers (except SS)
5109a163ed8SThomas Gleixner * are saved and zeroed this means that if the BIOS tries to reference
5119a163ed8SThomas Gleixner * any data without explicitly loading the segment registers, the kernel
5129a163ed8SThomas Gleixner * will fault immediately rather than have some unforeseen circumstances
5139a163ed8SThomas Gleixner * for the rest of the kernel. And it will be very obvious! :-) Doing
5149a163ed8SThomas Gleixner * this depends on CS referring to the same physical memory as DS so that
5159a163ed8SThomas Gleixner * DS can be zeroed before the call. Unfortunately, we can't do anything
5169a163ed8SThomas Gleixner * about the stack segment/pointer. Also, we tell the compiler that
5179a163ed8SThomas Gleixner * everything could change.
5189a163ed8SThomas Gleixner *
5199a163ed8SThomas Gleixner * Also, we KNOW that for the non error case of apm_bios_call, there
5209a163ed8SThomas Gleixner * is no useful data returned in the low order 8 bits of eax.
5219a163ed8SThomas Gleixner */
5229a163ed8SThomas Gleixner
__apm_irq_save(void)5239a163ed8SThomas Gleixner static inline unsigned long __apm_irq_save(void)
5249a163ed8SThomas Gleixner {
5259a163ed8SThomas Gleixner unsigned long flags;
5269a163ed8SThomas Gleixner local_save_flags(flags);
5279a163ed8SThomas Gleixner if (apm_info.allow_ints) {
5289a163ed8SThomas Gleixner if (irqs_disabled_flags(flags))
5299a163ed8SThomas Gleixner local_irq_enable();
5309a163ed8SThomas Gleixner } else
5319a163ed8SThomas Gleixner local_irq_disable();
5329a163ed8SThomas Gleixner
5339a163ed8SThomas Gleixner return flags;
5349a163ed8SThomas Gleixner }
5359a163ed8SThomas Gleixner
5369a163ed8SThomas Gleixner #define apm_irq_save(flags) \
5379a163ed8SThomas Gleixner do { flags = __apm_irq_save(); } while (0)
5389a163ed8SThomas Gleixner
apm_irq_restore(unsigned long flags)5399a163ed8SThomas Gleixner static inline void apm_irq_restore(unsigned long flags)
5409a163ed8SThomas Gleixner {
5419a163ed8SThomas Gleixner if (irqs_disabled_flags(flags))
5429a163ed8SThomas Gleixner local_irq_disable();
5439a163ed8SThomas Gleixner else if (irqs_disabled())
5449a163ed8SThomas Gleixner local_irq_enable();
5459a163ed8SThomas Gleixner }
5469a163ed8SThomas Gleixner
5479a163ed8SThomas Gleixner #ifdef APM_ZERO_SEGS
5489a163ed8SThomas Gleixner # define APM_DECL_SEGS \
5499a163ed8SThomas Gleixner unsigned int saved_fs; unsigned int saved_gs;
5509a163ed8SThomas Gleixner # define APM_DO_SAVE_SEGS \
5519a163ed8SThomas Gleixner savesegment(fs, saved_fs); savesegment(gs, saved_gs)
5529a163ed8SThomas Gleixner # define APM_DO_RESTORE_SEGS \
5539a163ed8SThomas Gleixner loadsegment(fs, saved_fs); loadsegment(gs, saved_gs)
5549a163ed8SThomas Gleixner #else
5559a163ed8SThomas Gleixner # define APM_DECL_SEGS
5569a163ed8SThomas Gleixner # define APM_DO_SAVE_SEGS
5579a163ed8SThomas Gleixner # define APM_DO_RESTORE_SEGS
5589a163ed8SThomas Gleixner #endif
5599a163ed8SThomas Gleixner
56089bd55d1SRusty Russell struct apm_bios_call {
56189bd55d1SRusty Russell u32 func;
56289bd55d1SRusty Russell /* In and out */
56389bd55d1SRusty Russell u32 ebx;
56489bd55d1SRusty Russell u32 ecx;
56589bd55d1SRusty Russell /* Out only */
56689bd55d1SRusty Russell u32 eax;
56789bd55d1SRusty Russell u32 edx;
56889bd55d1SRusty Russell u32 esi;
56989bd55d1SRusty Russell
57089bd55d1SRusty Russell /* Error: -ENOMEM, or bits 8-15 of eax */
57189bd55d1SRusty Russell int err;
57289bd55d1SRusty Russell };
57389bd55d1SRusty Russell
5749a163ed8SThomas Gleixner /**
57589bd55d1SRusty Russell * __apm_bios_call - Make an APM BIOS 32bit call
57689bd55d1SRusty Russell * @_call: pointer to struct apm_bios_call.
5779a163ed8SThomas Gleixner *
5789a163ed8SThomas Gleixner * Make an APM call using the 32bit protected mode interface. The
5799a163ed8SThomas Gleixner * caller is responsible for knowing if APM BIOS is configured and
5809a163ed8SThomas Gleixner * enabled. This call can disable interrupts for a long period of
5819a163ed8SThomas Gleixner * time on some laptops. The return value is in AH and the carry
5829a163ed8SThomas Gleixner * flag is loaded into AL. If there is an error, then the error
5839a163ed8SThomas Gleixner * code is returned in AH (bits 8-15 of eax) and this function
5849a163ed8SThomas Gleixner * returns non-zero.
58589bd55d1SRusty Russell *
58689bd55d1SRusty Russell * Note: this makes the call on the current CPU.
5879a163ed8SThomas Gleixner */
__apm_bios_call(void * _call)58889bd55d1SRusty Russell static long __apm_bios_call(void *_call)
5899a163ed8SThomas Gleixner {
5909a163ed8SThomas Gleixner APM_DECL_SEGS
5919a163ed8SThomas Gleixner unsigned long flags;
5929a163ed8SThomas Gleixner int cpu;
5939a163ed8SThomas Gleixner struct desc_struct save_desc_40;
5949a163ed8SThomas Gleixner struct desc_struct *gdt;
59589bd55d1SRusty Russell struct apm_bios_call *call = _call;
596fe379fa4SPeter Zijlstra u64 ibt;
5979a163ed8SThomas Gleixner
5989a163ed8SThomas Gleixner cpu = get_cpu();
59989bd55d1SRusty Russell BUG_ON(cpu != 0);
60069218e47SThomas Garnier gdt = get_cpu_gdt_rw(cpu);
6019a163ed8SThomas Gleixner save_desc_40 = gdt[0x40 / 8];
6029a163ed8SThomas Gleixner gdt[0x40 / 8] = bad_bios_desc;
6039a163ed8SThomas Gleixner
6049a163ed8SThomas Gleixner apm_irq_save(flags);
6056f6060a5SVille Syrjälä firmware_restrict_branch_speculation_start();
606*93be2859SArd Biesheuvel ibt = ibt_save(true);
6079a163ed8SThomas Gleixner APM_DO_SAVE_SEGS;
60889bd55d1SRusty Russell apm_bios_call_asm(call->func, call->ebx, call->ecx,
60989bd55d1SRusty Russell &call->eax, &call->ebx, &call->ecx, &call->edx,
61089bd55d1SRusty Russell &call->esi);
6119a163ed8SThomas Gleixner APM_DO_RESTORE_SEGS;
612fe379fa4SPeter Zijlstra ibt_restore(ibt);
6136f6060a5SVille Syrjälä firmware_restrict_branch_speculation_end();
6149a163ed8SThomas Gleixner apm_irq_restore(flags);
6159a163ed8SThomas Gleixner gdt[0x40 / 8] = save_desc_40;
6169a163ed8SThomas Gleixner put_cpu();
6179a163ed8SThomas Gleixner
61889bd55d1SRusty Russell return call->eax & 0xff;
61989bd55d1SRusty Russell }
62089bd55d1SRusty Russell
62189bd55d1SRusty Russell /* Run __apm_bios_call or __apm_bios_call_simple on CPU 0 */
on_cpu0(long (* fn)(void *),struct apm_bios_call * call)62289bd55d1SRusty Russell static int on_cpu0(long (*fn)(void *), struct apm_bios_call *call)
62389bd55d1SRusty Russell {
62489bd55d1SRusty Russell int ret;
62589bd55d1SRusty Russell
62689bd55d1SRusty Russell /* Don't bother with work_on_cpu in the common case, so we don't
62789bd55d1SRusty Russell * have to worry about OOM or overhead. */
62889bd55d1SRusty Russell if (get_cpu() == 0) {
62989bd55d1SRusty Russell ret = fn(call);
63089bd55d1SRusty Russell put_cpu();
63189bd55d1SRusty Russell } else {
63289bd55d1SRusty Russell put_cpu();
63389bd55d1SRusty Russell ret = work_on_cpu(0, fn, call);
63489bd55d1SRusty Russell }
63589bd55d1SRusty Russell
63689bd55d1SRusty Russell /* work_on_cpu can fail with -ENOMEM */
63789bd55d1SRusty Russell if (ret < 0)
63889bd55d1SRusty Russell call->err = ret;
63989bd55d1SRusty Russell else
64089bd55d1SRusty Russell call->err = (call->eax >> 8) & 0xff;
64189bd55d1SRusty Russell
64289bd55d1SRusty Russell return ret;
64389bd55d1SRusty Russell }
64489bd55d1SRusty Russell
64589bd55d1SRusty Russell /**
64689bd55d1SRusty Russell * apm_bios_call - Make an APM BIOS 32bit call (on CPU 0)
64789bd55d1SRusty Russell * @call: the apm_bios_call registers.
64889bd55d1SRusty Russell *
64989bd55d1SRusty Russell * If there is an error, it is returned in @call.err.
65089bd55d1SRusty Russell */
apm_bios_call(struct apm_bios_call * call)65189bd55d1SRusty Russell static int apm_bios_call(struct apm_bios_call *call)
65289bd55d1SRusty Russell {
65389bd55d1SRusty Russell return on_cpu0(__apm_bios_call, call);
65489bd55d1SRusty Russell }
65589bd55d1SRusty Russell
65689bd55d1SRusty Russell /**
65789bd55d1SRusty Russell * __apm_bios_call_simple - Make an APM BIOS 32bit call (on CPU 0)
65889bd55d1SRusty Russell * @_call: pointer to struct apm_bios_call.
65989bd55d1SRusty Russell *
66089bd55d1SRusty Russell * Make a BIOS call that returns one value only, or just status.
66189bd55d1SRusty Russell * If there is an error, then the error code is returned in AH
66289bd55d1SRusty Russell * (bits 8-15 of eax) and this function returns non-zero (it can
66389bd55d1SRusty Russell * also return -ENOMEM). This is used for simpler BIOS operations.
66489bd55d1SRusty Russell * This call may hold interrupts off for a long time on some laptops.
66589bd55d1SRusty Russell *
66689bd55d1SRusty Russell * Note: this makes the call on the current CPU.
66789bd55d1SRusty Russell */
__apm_bios_call_simple(void * _call)66889bd55d1SRusty Russell static long __apm_bios_call_simple(void *_call)
66989bd55d1SRusty Russell {
67089bd55d1SRusty Russell u8 error;
67189bd55d1SRusty Russell APM_DECL_SEGS
67289bd55d1SRusty Russell unsigned long flags;
67389bd55d1SRusty Russell int cpu;
67489bd55d1SRusty Russell struct desc_struct save_desc_40;
67589bd55d1SRusty Russell struct desc_struct *gdt;
67689bd55d1SRusty Russell struct apm_bios_call *call = _call;
677fe379fa4SPeter Zijlstra u64 ibt;
67889bd55d1SRusty Russell
67989bd55d1SRusty Russell cpu = get_cpu();
68089bd55d1SRusty Russell BUG_ON(cpu != 0);
68169218e47SThomas Garnier gdt = get_cpu_gdt_rw(cpu);
68289bd55d1SRusty Russell save_desc_40 = gdt[0x40 / 8];
68389bd55d1SRusty Russell gdt[0x40 / 8] = bad_bios_desc;
68489bd55d1SRusty Russell
68589bd55d1SRusty Russell apm_irq_save(flags);
6866f6060a5SVille Syrjälä firmware_restrict_branch_speculation_start();
687*93be2859SArd Biesheuvel ibt = ibt_save(true);
68889bd55d1SRusty Russell APM_DO_SAVE_SEGS;
68989bd55d1SRusty Russell error = apm_bios_call_simple_asm(call->func, call->ebx, call->ecx,
69089bd55d1SRusty Russell &call->eax);
69189bd55d1SRusty Russell APM_DO_RESTORE_SEGS;
692fe379fa4SPeter Zijlstra ibt_restore(ibt);
6936f6060a5SVille Syrjälä firmware_restrict_branch_speculation_end();
69489bd55d1SRusty Russell apm_irq_restore(flags);
69589bd55d1SRusty Russell gdt[0x40 / 8] = save_desc_40;
69689bd55d1SRusty Russell put_cpu();
69789bd55d1SRusty Russell return error;
6989a163ed8SThomas Gleixner }
6999a163ed8SThomas Gleixner
7009a163ed8SThomas Gleixner /**
7019a163ed8SThomas Gleixner * apm_bios_call_simple - make a simple APM BIOS 32bit call
7029a163ed8SThomas Gleixner * @func: APM function to invoke
7039a163ed8SThomas Gleixner * @ebx_in: EBX register value for BIOS call
7049a163ed8SThomas Gleixner * @ecx_in: ECX register value for BIOS call
7059a163ed8SThomas Gleixner * @eax: EAX register on return from the BIOS call
70689bd55d1SRusty Russell * @err: bits
7079a163ed8SThomas Gleixner *
7089a163ed8SThomas Gleixner * Make a BIOS call that returns one value only, or just status.
70989bd55d1SRusty Russell * If there is an error, then the error code is returned in @err
71089bd55d1SRusty Russell * and this function returns non-zero. This is used for simpler
71189bd55d1SRusty Russell * BIOS operations. This call may hold interrupts off for a long
71289bd55d1SRusty Russell * time on some laptops.
7139a163ed8SThomas Gleixner */
apm_bios_call_simple(u32 func,u32 ebx_in,u32 ecx_in,u32 * eax,int * err)71489bd55d1SRusty Russell static int apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax,
71589bd55d1SRusty Russell int *err)
7169a163ed8SThomas Gleixner {
71789bd55d1SRusty Russell struct apm_bios_call call;
71889bd55d1SRusty Russell int ret;
7199a163ed8SThomas Gleixner
72089bd55d1SRusty Russell call.func = func;
72189bd55d1SRusty Russell call.ebx = ebx_in;
72289bd55d1SRusty Russell call.ecx = ecx_in;
7239a163ed8SThomas Gleixner
72489bd55d1SRusty Russell ret = on_cpu0(__apm_bios_call_simple, &call);
72589bd55d1SRusty Russell *eax = call.eax;
72689bd55d1SRusty Russell *err = call.err;
72789bd55d1SRusty Russell return ret;
7289a163ed8SThomas Gleixner }
7299a163ed8SThomas Gleixner
7309a163ed8SThomas Gleixner /**
7319a163ed8SThomas Gleixner * apm_driver_version - APM driver version
7329a163ed8SThomas Gleixner * @val: loaded with the APM version on return
7339a163ed8SThomas Gleixner *
7349a163ed8SThomas Gleixner * Retrieve the APM version supported by the BIOS. This is only
7359a163ed8SThomas Gleixner * supported for APM 1.1 or higher. An error indicates APM 1.0 is
7369a163ed8SThomas Gleixner * probably present.
7379a163ed8SThomas Gleixner *
7389a163ed8SThomas Gleixner * On entry val should point to a value indicating the APM driver
7399a163ed8SThomas Gleixner * version with the high byte being the major and the low byte the
7409a163ed8SThomas Gleixner * minor number both in BCD
7419a163ed8SThomas Gleixner *
7429a163ed8SThomas Gleixner * On return it will hold the BIOS revision supported in the
7439a163ed8SThomas Gleixner * same format.
7449a163ed8SThomas Gleixner */
7459a163ed8SThomas Gleixner
apm_driver_version(u_short * val)7469a163ed8SThomas Gleixner static int apm_driver_version(u_short *val)
7479a163ed8SThomas Gleixner {
7489a163ed8SThomas Gleixner u32 eax;
74989bd55d1SRusty Russell int err;
7509a163ed8SThomas Gleixner
75189bd55d1SRusty Russell if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax, &err))
75289bd55d1SRusty Russell return err;
7539a163ed8SThomas Gleixner *val = eax;
7549a163ed8SThomas Gleixner return APM_SUCCESS;
7559a163ed8SThomas Gleixner }
7569a163ed8SThomas Gleixner
7579a163ed8SThomas Gleixner /**
7589a163ed8SThomas Gleixner * apm_get_event - get an APM event from the BIOS
7599a163ed8SThomas Gleixner * @event: pointer to the event
7609a163ed8SThomas Gleixner * @info: point to the event information
7619a163ed8SThomas Gleixner *
7629a163ed8SThomas Gleixner * The APM BIOS provides a polled information for event
7639a163ed8SThomas Gleixner * reporting. The BIOS expects to be polled at least every second
7649a163ed8SThomas Gleixner * when events are pending. When a message is found the caller should
7659a163ed8SThomas Gleixner * poll until no more messages are present. However, this causes
7669a163ed8SThomas Gleixner * problems on some laptops where a suspend event notification is
7679a163ed8SThomas Gleixner * not cleared until it is acknowledged.
7689a163ed8SThomas Gleixner *
7699a163ed8SThomas Gleixner * Additional information is returned in the info pointer, providing
770d9f6e12fSIngo Molnar * that APM 1.2 is in use. If no messages are pending the value 0x80
7719a163ed8SThomas Gleixner * is returned (No power management events pending).
7729a163ed8SThomas Gleixner */
apm_get_event(apm_event_t * event,apm_eventinfo_t * info)7739a163ed8SThomas Gleixner static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
7749a163ed8SThomas Gleixner {
77589bd55d1SRusty Russell struct apm_bios_call call;
7769a163ed8SThomas Gleixner
77789bd55d1SRusty Russell call.func = APM_FUNC_GET_EVENT;
77889bd55d1SRusty Russell call.ebx = call.ecx = 0;
77989bd55d1SRusty Russell
78089bd55d1SRusty Russell if (apm_bios_call(&call))
78189bd55d1SRusty Russell return call.err;
78289bd55d1SRusty Russell
78389bd55d1SRusty Russell *event = call.ebx;
7849a163ed8SThomas Gleixner if (apm_info.connection_version < 0x0102)
7859a163ed8SThomas Gleixner *info = ~0; /* indicate info not valid */
7869a163ed8SThomas Gleixner else
78789bd55d1SRusty Russell *info = call.ecx;
7889a163ed8SThomas Gleixner return APM_SUCCESS;
7899a163ed8SThomas Gleixner }
7909a163ed8SThomas Gleixner
7919a163ed8SThomas Gleixner /**
7929a163ed8SThomas Gleixner * set_power_state - set the power management state
7939a163ed8SThomas Gleixner * @what: which items to transition
7949a163ed8SThomas Gleixner * @state: state to transition to
7959a163ed8SThomas Gleixner *
7969a163ed8SThomas Gleixner * Request an APM change of state for one or more system devices. The
7979a163ed8SThomas Gleixner * processor state must be transitioned last of all. what holds the
7989a163ed8SThomas Gleixner * class of device in the upper byte and the device number (0xFF for
7999a163ed8SThomas Gleixner * all) for the object to be transitioned.
8009a163ed8SThomas Gleixner *
8019a163ed8SThomas Gleixner * The state holds the state to transition to, which may in fact
8029a163ed8SThomas Gleixner * be an acceptance of a BIOS requested state change.
8039a163ed8SThomas Gleixner */
8049a163ed8SThomas Gleixner
set_power_state(u_short what,u_short state)8059a163ed8SThomas Gleixner static int set_power_state(u_short what, u_short state)
8069a163ed8SThomas Gleixner {
8079a163ed8SThomas Gleixner u32 eax;
80889bd55d1SRusty Russell int err;
8099a163ed8SThomas Gleixner
81089bd55d1SRusty Russell if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax, &err))
81189bd55d1SRusty Russell return err;
8129a163ed8SThomas Gleixner return APM_SUCCESS;
8139a163ed8SThomas Gleixner }
8149a163ed8SThomas Gleixner
8159a163ed8SThomas Gleixner /**
8169a163ed8SThomas Gleixner * set_system_power_state - set system wide power state
8179a163ed8SThomas Gleixner * @state: which state to enter
8189a163ed8SThomas Gleixner *
8199a163ed8SThomas Gleixner * Transition the entire system into a new APM power state.
8209a163ed8SThomas Gleixner */
8219a163ed8SThomas Gleixner
set_system_power_state(u_short state)8229a163ed8SThomas Gleixner static int set_system_power_state(u_short state)
8239a163ed8SThomas Gleixner {
8249a163ed8SThomas Gleixner return set_power_state(APM_DEVICE_ALL, state);
8259a163ed8SThomas Gleixner }
8269a163ed8SThomas Gleixner
8279a163ed8SThomas Gleixner /**
8289a163ed8SThomas Gleixner * apm_do_idle - perform power saving
8299a163ed8SThomas Gleixner *
8309a163ed8SThomas Gleixner * This function notifies the BIOS that the processor is (in the view
8319a163ed8SThomas Gleixner * of the OS) idle. It returns -1 in the event that the BIOS refuses
8329a163ed8SThomas Gleixner * to handle the idle request. On a success the function returns 1
8339a163ed8SThomas Gleixner * if the BIOS did clock slowing or 0 otherwise.
8349a163ed8SThomas Gleixner */
8359a163ed8SThomas Gleixner
apm_do_idle(void)8369a163ed8SThomas Gleixner static int apm_do_idle(void)
8379a163ed8SThomas Gleixner {
8389a163ed8SThomas Gleixner u32 eax;
8399a163ed8SThomas Gleixner u8 ret = 0;
8409a163ed8SThomas Gleixner int idled = 0;
841dc731fbbSSubrata Modak int err = 0;
8429a163ed8SThomas Gleixner
8439a163ed8SThomas Gleixner if (!need_resched()) {
8449a163ed8SThomas Gleixner idled = 1;
84589bd55d1SRusty Russell ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax, &err);
8469a163ed8SThomas Gleixner }
8479a163ed8SThomas Gleixner
8489a163ed8SThomas Gleixner if (!idled)
8499a163ed8SThomas Gleixner return 0;
8509a163ed8SThomas Gleixner
8519a163ed8SThomas Gleixner if (ret) {
8529a163ed8SThomas Gleixner static unsigned long t;
8539a163ed8SThomas Gleixner
8549a163ed8SThomas Gleixner /* This always fails on some SMP boards running UP kernels.
8559a163ed8SThomas Gleixner * Only report the failure the first 5 times.
8569a163ed8SThomas Gleixner */
8573f4380a1SCyrill Gorcunov if (++t < 5) {
85889bd55d1SRusty Russell printk(KERN_DEBUG "apm_do_idle failed (%d)\n", err);
8599a163ed8SThomas Gleixner t = jiffies;
8609a163ed8SThomas Gleixner }
8619a163ed8SThomas Gleixner return -1;
8629a163ed8SThomas Gleixner }
8639a163ed8SThomas Gleixner clock_slowed = (apm_info.bios.flags & APM_IDLE_SLOWS_CLOCK) != 0;
8649a163ed8SThomas Gleixner return clock_slowed;
8659a163ed8SThomas Gleixner }
8669a163ed8SThomas Gleixner
8679a163ed8SThomas Gleixner /**
8689a163ed8SThomas Gleixner * apm_do_busy - inform the BIOS the CPU is busy
8699a163ed8SThomas Gleixner *
8709a163ed8SThomas Gleixner * Request that the BIOS brings the CPU back to full performance.
8719a163ed8SThomas Gleixner */
8729a163ed8SThomas Gleixner
apm_do_busy(void)8739a163ed8SThomas Gleixner static void apm_do_busy(void)
8749a163ed8SThomas Gleixner {
8759a163ed8SThomas Gleixner u32 dummy;
87689bd55d1SRusty Russell int err;
8779a163ed8SThomas Gleixner
8789a163ed8SThomas Gleixner if (clock_slowed || ALWAYS_CALL_BUSY) {
87989bd55d1SRusty Russell (void)apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy, &err);
8809a163ed8SThomas Gleixner clock_slowed = 0;
8819a163ed8SThomas Gleixner }
8829a163ed8SThomas Gleixner }
8839a163ed8SThomas Gleixner
8849a163ed8SThomas Gleixner /*
8859a163ed8SThomas Gleixner * If no process has really been interested in
8869a163ed8SThomas Gleixner * the CPU for some time, we want to call BIOS
8879a163ed8SThomas Gleixner * power management - we probably want
8889a163ed8SThomas Gleixner * to conserve power.
8899a163ed8SThomas Gleixner */
8909a163ed8SThomas Gleixner #define IDLE_CALC_LIMIT (HZ * 100)
8919a163ed8SThomas Gleixner #define IDLE_LEAKY_MAX 16
8929a163ed8SThomas Gleixner
8939a163ed8SThomas Gleixner /**
8949a163ed8SThomas Gleixner * apm_cpu_idle - cpu idling for APM capable Linux
8959a163ed8SThomas Gleixner *
8969a163ed8SThomas Gleixner * This is the idling function the kernel executes when APM is available. It
8979a163ed8SThomas Gleixner * tries to do BIOS powermanagement based on the average system idle time.
8989a163ed8SThomas Gleixner * Furthermore it calls the system default idle routine.
8999a163ed8SThomas Gleixner */
9009a163ed8SThomas Gleixner
apm_cpu_idle(struct cpuidle_device * dev,struct cpuidle_driver * drv,int index)901dd8af076SLen Brown static int apm_cpu_idle(struct cpuidle_device *dev,
902dd8af076SLen Brown struct cpuidle_driver *drv, int index)
9039a163ed8SThomas Gleixner {
9049a163ed8SThomas Gleixner static int use_apm_idle; /* = 0 */
9059a163ed8SThomas Gleixner static unsigned int last_jiffies; /* = 0 */
906f7dcd63dSFrederic Weisbecker static u64 last_stime; /* = 0 */
907f7dcd63dSFrederic Weisbecker u64 stime, utime;
9089a163ed8SThomas Gleixner
9099a163ed8SThomas Gleixner int apm_idle_done = 0;
9109a163ed8SThomas Gleixner unsigned int jiffies_since_last_check = jiffies - last_jiffies;
9119a163ed8SThomas Gleixner unsigned int bucket;
9129a163ed8SThomas Gleixner
9139a163ed8SThomas Gleixner recalc:
914f7dcd63dSFrederic Weisbecker task_cputime(current, &utime, &stime);
9159a163ed8SThomas Gleixner if (jiffies_since_last_check > IDLE_CALC_LIMIT) {
9169a163ed8SThomas Gleixner use_apm_idle = 0;
9179a163ed8SThomas Gleixner } else if (jiffies_since_last_check > idle_period) {
9189a163ed8SThomas Gleixner unsigned int idle_percentage;
9199a163ed8SThomas Gleixner
920f7dcd63dSFrederic Weisbecker idle_percentage = nsecs_to_jiffies(stime - last_stime);
9219a163ed8SThomas Gleixner idle_percentage *= 100;
9229a163ed8SThomas Gleixner idle_percentage /= jiffies_since_last_check;
9239a163ed8SThomas Gleixner use_apm_idle = (idle_percentage > idle_threshold);
9249a163ed8SThomas Gleixner if (apm_info.forbid_idle)
9259a163ed8SThomas Gleixner use_apm_idle = 0;
9269a163ed8SThomas Gleixner }
9279a163ed8SThomas Gleixner
9286fac4829SFrederic Weisbecker last_jiffies = jiffies;
9296fac4829SFrederic Weisbecker last_stime = stime;
9306fac4829SFrederic Weisbecker
9319a163ed8SThomas Gleixner bucket = IDLE_LEAKY_MAX;
9329a163ed8SThomas Gleixner
9339a163ed8SThomas Gleixner while (!need_resched()) {
9349a163ed8SThomas Gleixner if (use_apm_idle) {
9359a163ed8SThomas Gleixner unsigned int t;
9369a163ed8SThomas Gleixner
9379a163ed8SThomas Gleixner t = jiffies;
9389a163ed8SThomas Gleixner switch (apm_do_idle()) {
9393f4380a1SCyrill Gorcunov case 0:
9403f4380a1SCyrill Gorcunov apm_idle_done = 1;
9419a163ed8SThomas Gleixner if (t != jiffies) {
9429a163ed8SThomas Gleixner if (bucket) {
9439a163ed8SThomas Gleixner bucket = IDLE_LEAKY_MAX;
9449a163ed8SThomas Gleixner continue;
9459a163ed8SThomas Gleixner }
9469a163ed8SThomas Gleixner } else if (bucket) {
9479a163ed8SThomas Gleixner bucket--;
9489a163ed8SThomas Gleixner continue;
9499a163ed8SThomas Gleixner }
9509a163ed8SThomas Gleixner break;
9513f4380a1SCyrill Gorcunov case 1:
9523f4380a1SCyrill Gorcunov apm_idle_done = 1;
9539a163ed8SThomas Gleixner break;
9549a163ed8SThomas Gleixner default: /* BIOS refused */
9559a163ed8SThomas Gleixner break;
9569a163ed8SThomas Gleixner }
9579a163ed8SThomas Gleixner }
9589a163ed8SThomas Gleixner default_idle();
9597f424a8bSPeter Zijlstra local_irq_disable();
9609a163ed8SThomas Gleixner jiffies_since_last_check = jiffies - last_jiffies;
9619a163ed8SThomas Gleixner if (jiffies_since_last_check > idle_period)
9629a163ed8SThomas Gleixner goto recalc;
9639a163ed8SThomas Gleixner }
9649a163ed8SThomas Gleixner
9659a163ed8SThomas Gleixner if (apm_idle_done)
9669a163ed8SThomas Gleixner apm_do_busy();
9677f424a8bSPeter Zijlstra
968dd8af076SLen Brown return index;
9699a163ed8SThomas Gleixner }
9709a163ed8SThomas Gleixner
9719a163ed8SThomas Gleixner /**
9729a163ed8SThomas Gleixner * apm_power_off - ask the BIOS to power off
9739a163ed8SThomas Gleixner *
9749a163ed8SThomas Gleixner * Handle the power off sequence. This is the one piece of code we
9759a163ed8SThomas Gleixner * will execute even on SMP machines. In order to deal with BIOS
9769a163ed8SThomas Gleixner * bugs we support real mode APM BIOS power off calls. We also make
9779a163ed8SThomas Gleixner * the SMP call on CPU0 as some systems will only honour this call
9789a163ed8SThomas Gleixner * on their first cpu.
9799a163ed8SThomas Gleixner */
9809a163ed8SThomas Gleixner
apm_power_off(void)9819a163ed8SThomas Gleixner static void apm_power_off(void)
9829a163ed8SThomas Gleixner {
9839a163ed8SThomas Gleixner /* Some bioses don't like being called from CPU != 0 */
9843f4380a1SCyrill Gorcunov if (apm_info.realmode_power_off) {
98589bd55d1SRusty Russell set_cpus_allowed_ptr(current, cpumask_of(0));
9863d35ac34SH. Peter Anvin machine_real_restart(MRR_APM);
9873f4380a1SCyrill Gorcunov } else {
9889a163ed8SThomas Gleixner (void)set_system_power_state(APM_STATE_OFF);
9899a163ed8SThomas Gleixner }
9903f4380a1SCyrill Gorcunov }
9919a163ed8SThomas Gleixner
9929a163ed8SThomas Gleixner #ifdef CONFIG_APM_DO_ENABLE
9939a163ed8SThomas Gleixner
9949a163ed8SThomas Gleixner /**
9959a163ed8SThomas Gleixner * apm_enable_power_management - enable BIOS APM power management
9969a163ed8SThomas Gleixner * @enable: enable yes/no
9979a163ed8SThomas Gleixner *
9989a163ed8SThomas Gleixner * Enable or disable the APM BIOS power services.
9999a163ed8SThomas Gleixner */
10009a163ed8SThomas Gleixner
apm_enable_power_management(int enable)10019a163ed8SThomas Gleixner static int apm_enable_power_management(int enable)
10029a163ed8SThomas Gleixner {
10039a163ed8SThomas Gleixner u32 eax;
100489bd55d1SRusty Russell int err;
10059a163ed8SThomas Gleixner
10069a163ed8SThomas Gleixner if ((enable == 0) && (apm_info.bios.flags & APM_BIOS_DISENGAGED))
10079a163ed8SThomas Gleixner return APM_NOT_ENGAGED;
10089a163ed8SThomas Gleixner if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
100989bd55d1SRusty Russell enable, &eax, &err))
101089bd55d1SRusty Russell return err;
10119a163ed8SThomas Gleixner if (enable)
10129a163ed8SThomas Gleixner apm_info.bios.flags &= ~APM_BIOS_DISABLED;
10139a163ed8SThomas Gleixner else
10149a163ed8SThomas Gleixner apm_info.bios.flags |= APM_BIOS_DISABLED;
10159a163ed8SThomas Gleixner return APM_SUCCESS;
10169a163ed8SThomas Gleixner }
10179a163ed8SThomas Gleixner #endif
10189a163ed8SThomas Gleixner
10199a163ed8SThomas Gleixner /**
10209a163ed8SThomas Gleixner * apm_get_power_status - get current power state
10219a163ed8SThomas Gleixner * @status: returned status
10229a163ed8SThomas Gleixner * @bat: battery info
10239a163ed8SThomas Gleixner * @life: estimated life
10249a163ed8SThomas Gleixner *
10259a163ed8SThomas Gleixner * Obtain the current power status from the APM BIOS. We return a
10269a163ed8SThomas Gleixner * status which gives the rough battery status, and current power
10279a163ed8SThomas Gleixner * source. The bat value returned give an estimate as a percentage
10289a163ed8SThomas Gleixner * of life and a status value for the battery. The estimated life
1029163b0991SIngo Molnar * if reported is a lifetime in seconds/minutes at current power
10309a163ed8SThomas Gleixner * consumption.
10319a163ed8SThomas Gleixner */
10329a163ed8SThomas Gleixner
apm_get_power_status(u_short * status,u_short * bat,u_short * life)10339a163ed8SThomas Gleixner static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
10349a163ed8SThomas Gleixner {
103589bd55d1SRusty Russell struct apm_bios_call call;
103689bd55d1SRusty Russell
103789bd55d1SRusty Russell call.func = APM_FUNC_GET_STATUS;
103889bd55d1SRusty Russell call.ebx = APM_DEVICE_ALL;
103989bd55d1SRusty Russell call.ecx = 0;
10409a163ed8SThomas Gleixner
10419a163ed8SThomas Gleixner if (apm_info.get_power_status_broken)
10429a163ed8SThomas Gleixner return APM_32_UNSUPPORTED;
10433a6d8676SArnd Bergmann if (apm_bios_call(&call)) {
10443a6d8676SArnd Bergmann if (!call.err)
10453a6d8676SArnd Bergmann return APM_NO_ERROR;
104689bd55d1SRusty Russell return call.err;
10473a6d8676SArnd Bergmann }
104889bd55d1SRusty Russell *status = call.ebx;
104989bd55d1SRusty Russell *bat = call.ecx;
10509a163ed8SThomas Gleixner if (apm_info.get_power_status_swabinminutes) {
105189bd55d1SRusty Russell *life = swab16((u16)call.edx);
10529a163ed8SThomas Gleixner *life |= 0x8000;
10539a163ed8SThomas Gleixner } else
105489bd55d1SRusty Russell *life = call.edx;
10559a163ed8SThomas Gleixner return APM_SUCCESS;
10569a163ed8SThomas Gleixner }
10579a163ed8SThomas Gleixner
10589a163ed8SThomas Gleixner #if 0
10599a163ed8SThomas Gleixner static int apm_get_battery_status(u_short which, u_short *status,
10609a163ed8SThomas Gleixner u_short *bat, u_short *life, u_short *nbat)
10619a163ed8SThomas Gleixner {
10629a163ed8SThomas Gleixner u32 eax;
10639a163ed8SThomas Gleixner u32 ebx;
10649a163ed8SThomas Gleixner u32 ecx;
10659a163ed8SThomas Gleixner u32 edx;
10669a163ed8SThomas Gleixner u32 esi;
10679a163ed8SThomas Gleixner
10689a163ed8SThomas Gleixner if (apm_info.connection_version < 0x0102) {
10699a163ed8SThomas Gleixner /* pretend we only have one battery. */
10709a163ed8SThomas Gleixner if (which != 1)
10719a163ed8SThomas Gleixner return APM_BAD_DEVICE;
10729a163ed8SThomas Gleixner *nbat = 1;
10739a163ed8SThomas Gleixner return apm_get_power_status(status, bat, life);
10749a163ed8SThomas Gleixner }
10759a163ed8SThomas Gleixner
10769a163ed8SThomas Gleixner if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax,
10779a163ed8SThomas Gleixner &ebx, &ecx, &edx, &esi))
10789a163ed8SThomas Gleixner return (eax >> 8) & 0xff;
10799a163ed8SThomas Gleixner *status = ebx;
10809a163ed8SThomas Gleixner *bat = ecx;
10819a163ed8SThomas Gleixner *life = edx;
10829a163ed8SThomas Gleixner *nbat = esi;
10839a163ed8SThomas Gleixner return APM_SUCCESS;
10849a163ed8SThomas Gleixner }
10859a163ed8SThomas Gleixner #endif
10869a163ed8SThomas Gleixner
10879a163ed8SThomas Gleixner /**
10889a163ed8SThomas Gleixner * apm_engage_power_management - enable PM on a device
10899a163ed8SThomas Gleixner * @device: identity of device
10909a163ed8SThomas Gleixner * @enable: on/off
10919a163ed8SThomas Gleixner *
10926a6256f9SAdam Buchbinder * Activate or deactivate power management on either a specific device
10939a163ed8SThomas Gleixner * or the entire system (%APM_DEVICE_ALL).
10949a163ed8SThomas Gleixner */
10959a163ed8SThomas Gleixner
apm_engage_power_management(u_short device,int enable)10969a163ed8SThomas Gleixner static int apm_engage_power_management(u_short device, int enable)
10979a163ed8SThomas Gleixner {
10989a163ed8SThomas Gleixner u32 eax;
109989bd55d1SRusty Russell int err;
11009a163ed8SThomas Gleixner
11019a163ed8SThomas Gleixner if ((enable == 0) && (device == APM_DEVICE_ALL)
11029a163ed8SThomas Gleixner && (apm_info.bios.flags & APM_BIOS_DISABLED))
11039a163ed8SThomas Gleixner return APM_DISABLED;
110489bd55d1SRusty Russell if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable,
110589bd55d1SRusty Russell &eax, &err))
110689bd55d1SRusty Russell return err;
11079a163ed8SThomas Gleixner if (device == APM_DEVICE_ALL) {
11089a163ed8SThomas Gleixner if (enable)
11099a163ed8SThomas Gleixner apm_info.bios.flags &= ~APM_BIOS_DISENGAGED;
11109a163ed8SThomas Gleixner else
11119a163ed8SThomas Gleixner apm_info.bios.flags |= APM_BIOS_DISENGAGED;
11129a163ed8SThomas Gleixner }
11139a163ed8SThomas Gleixner return APM_SUCCESS;
11149a163ed8SThomas Gleixner }
11159a163ed8SThomas Gleixner
11169a163ed8SThomas Gleixner #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
11179a163ed8SThomas Gleixner
11189a163ed8SThomas Gleixner /**
11199a163ed8SThomas Gleixner * apm_console_blank - blank the display
11209a163ed8SThomas Gleixner * @blank: on/off
11219a163ed8SThomas Gleixner *
11229a163ed8SThomas Gleixner * Attempt to blank the console, firstly by blanking just video device
11239a163ed8SThomas Gleixner * zero, and if that fails (some BIOSes don't support it) then it blanks
11249a163ed8SThomas Gleixner * all video devices. Typically the BIOS will do laptop backlight and
11259a163ed8SThomas Gleixner * monitor powerdown for us.
11269a163ed8SThomas Gleixner */
11279a163ed8SThomas Gleixner
apm_console_blank(int blank)11289a163ed8SThomas Gleixner static int apm_console_blank(int blank)
11299a163ed8SThomas Gleixner {
11309a163ed8SThomas Gleixner int error = APM_NOT_ENGAGED; /* silence gcc */
11319a163ed8SThomas Gleixner int i;
11329a163ed8SThomas Gleixner u_short state;
11339a163ed8SThomas Gleixner static const u_short dev[3] = { 0x100, 0x1FF, 0x101 };
11349a163ed8SThomas Gleixner
11359a163ed8SThomas Gleixner state = blank ? APM_STATE_STANDBY : APM_STATE_READY;
11369a163ed8SThomas Gleixner
11379a163ed8SThomas Gleixner for (i = 0; i < ARRAY_SIZE(dev); i++) {
11389a163ed8SThomas Gleixner error = set_power_state(dev[i], state);
11399a163ed8SThomas Gleixner
11409a163ed8SThomas Gleixner if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
11419a163ed8SThomas Gleixner return 1;
11429a163ed8SThomas Gleixner
11439a163ed8SThomas Gleixner if (error == APM_NOT_ENGAGED)
11449a163ed8SThomas Gleixner break;
11459a163ed8SThomas Gleixner }
11469a163ed8SThomas Gleixner
11479a163ed8SThomas Gleixner if (error == APM_NOT_ENGAGED) {
11489a163ed8SThomas Gleixner static int tried;
11499a163ed8SThomas Gleixner int eng_error;
11509a163ed8SThomas Gleixner if (tried++ == 0) {
11519a163ed8SThomas Gleixner eng_error = apm_engage_power_management(APM_DEVICE_ALL, 1);
11529a163ed8SThomas Gleixner if (eng_error) {
11539a163ed8SThomas Gleixner apm_error("set display", error);
11549a163ed8SThomas Gleixner apm_error("engage interface", eng_error);
11559a163ed8SThomas Gleixner return 0;
11569a163ed8SThomas Gleixner } else
11579a163ed8SThomas Gleixner return apm_console_blank(blank);
11589a163ed8SThomas Gleixner }
11599a163ed8SThomas Gleixner }
11609a163ed8SThomas Gleixner apm_error("set display", error);
11619a163ed8SThomas Gleixner return 0;
11629a163ed8SThomas Gleixner }
11639a163ed8SThomas Gleixner #endif
11649a163ed8SThomas Gleixner
queue_empty(struct apm_user * as)11659a163ed8SThomas Gleixner static int queue_empty(struct apm_user *as)
11669a163ed8SThomas Gleixner {
11679a163ed8SThomas Gleixner return as->event_head == as->event_tail;
11689a163ed8SThomas Gleixner }
11699a163ed8SThomas Gleixner
get_queued_event(struct apm_user * as)11709a163ed8SThomas Gleixner static apm_event_t get_queued_event(struct apm_user *as)
11719a163ed8SThomas Gleixner {
11729a163ed8SThomas Gleixner if (++as->event_tail >= APM_MAX_EVENTS)
11739a163ed8SThomas Gleixner as->event_tail = 0;
11749a163ed8SThomas Gleixner return as->events[as->event_tail];
11759a163ed8SThomas Gleixner }
11769a163ed8SThomas Gleixner
queue_event(apm_event_t event,struct apm_user * sender)11779a163ed8SThomas Gleixner static void queue_event(apm_event_t event, struct apm_user *sender)
11789a163ed8SThomas Gleixner {
11799a163ed8SThomas Gleixner struct apm_user *as;
11809a163ed8SThomas Gleixner
11819a163ed8SThomas Gleixner spin_lock(&user_list_lock);
11829a163ed8SThomas Gleixner if (user_list == NULL)
11839a163ed8SThomas Gleixner goto out;
11849a163ed8SThomas Gleixner for (as = user_list; as != NULL; as = as->next) {
11859a163ed8SThomas Gleixner if ((as == sender) || (!as->reader))
11869a163ed8SThomas Gleixner continue;
11879a163ed8SThomas Gleixner if (++as->event_head >= APM_MAX_EVENTS)
11889a163ed8SThomas Gleixner as->event_head = 0;
11899a163ed8SThomas Gleixner
11909a163ed8SThomas Gleixner if (as->event_head == as->event_tail) {
11919a163ed8SThomas Gleixner static int notified;
11929a163ed8SThomas Gleixner
11939a163ed8SThomas Gleixner if (notified++ == 0)
1194c767a54bSJoe Perches pr_err("an event queue overflowed\n");
11959a163ed8SThomas Gleixner if (++as->event_tail >= APM_MAX_EVENTS)
11969a163ed8SThomas Gleixner as->event_tail = 0;
11979a163ed8SThomas Gleixner }
11989a163ed8SThomas Gleixner as->events[as->event_head] = event;
1199b764a15fSAlan Cox if (!as->suser || !as->writer)
12009a163ed8SThomas Gleixner continue;
12019a163ed8SThomas Gleixner switch (event) {
12029a163ed8SThomas Gleixner case APM_SYS_SUSPEND:
12039a163ed8SThomas Gleixner case APM_USER_SUSPEND:
12049a163ed8SThomas Gleixner as->suspends_pending++;
12059a163ed8SThomas Gleixner suspends_pending++;
12069a163ed8SThomas Gleixner break;
12079a163ed8SThomas Gleixner
12089a163ed8SThomas Gleixner case APM_SYS_STANDBY:
12099a163ed8SThomas Gleixner case APM_USER_STANDBY:
12109a163ed8SThomas Gleixner as->standbys_pending++;
12119a163ed8SThomas Gleixner standbys_pending++;
12129a163ed8SThomas Gleixner break;
12139a163ed8SThomas Gleixner }
12149a163ed8SThomas Gleixner }
12159a163ed8SThomas Gleixner wake_up_interruptible(&apm_waitqueue);
12169a163ed8SThomas Gleixner out:
12179a163ed8SThomas Gleixner spin_unlock(&user_list_lock);
12189a163ed8SThomas Gleixner }
12199a163ed8SThomas Gleixner
reinit_timer(void)12209a163ed8SThomas Gleixner static void reinit_timer(void)
12219a163ed8SThomas Gleixner {
12229a163ed8SThomas Gleixner #ifdef INIT_TIMER_AFTER_SUSPEND
12239a163ed8SThomas Gleixner unsigned long flags;
12249a163ed8SThomas Gleixner
1225ced918ebSThomas Gleixner raw_spin_lock_irqsave(&i8253_lock, flags);
12269a163ed8SThomas Gleixner /* set the clock to HZ */
122701898e3eSThomas Gleixner outb_p(0x34, PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
12289a163ed8SThomas Gleixner udelay(10);
122901898e3eSThomas Gleixner outb_p(LATCH & 0xff, PIT_CH0); /* LSB */
12309a163ed8SThomas Gleixner udelay(10);
123101898e3eSThomas Gleixner outb_p(LATCH >> 8, PIT_CH0); /* MSB */
12329a163ed8SThomas Gleixner udelay(10);
1233ced918ebSThomas Gleixner raw_spin_unlock_irqrestore(&i8253_lock, flags);
12349a163ed8SThomas Gleixner #endif
12359a163ed8SThomas Gleixner }
12369a163ed8SThomas Gleixner
suspend(int vetoable)12379a163ed8SThomas Gleixner static int suspend(int vetoable)
12389a163ed8SThomas Gleixner {
12399a163ed8SThomas Gleixner int err;
12409a163ed8SThomas Gleixner struct apm_user *as;
12419a163ed8SThomas Gleixner
1242d1616302SAlan Stern dpm_suspend_start(PMSG_SUSPEND);
1243cf579dfbSRafael J. Wysocki dpm_suspend_end(PMSG_SUSPEND);
12442ed8d2b3SRafael J. Wysocki
12452ed8d2b3SRafael J. Wysocki local_irq_disable();
124619234c08SRafael J. Wysocki syscore_suspend();
12479a163ed8SThomas Gleixner
12489a163ed8SThomas Gleixner local_irq_enable();
12499a163ed8SThomas Gleixner
12509a163ed8SThomas Gleixner save_processor_state();
12519a163ed8SThomas Gleixner err = set_system_power_state(APM_STATE_SUSPEND);
12529a163ed8SThomas Gleixner ignore_normal_resume = 1;
12539a163ed8SThomas Gleixner restore_processor_state();
12549a163ed8SThomas Gleixner
12559a163ed8SThomas Gleixner local_irq_disable();
12569a163ed8SThomas Gleixner reinit_timer();
12579a163ed8SThomas Gleixner
12589a163ed8SThomas Gleixner if (err == APM_NO_ERROR)
12599a163ed8SThomas Gleixner err = APM_SUCCESS;
12609a163ed8SThomas Gleixner if (err != APM_SUCCESS)
12619a163ed8SThomas Gleixner apm_error("suspend", err);
12629a163ed8SThomas Gleixner err = (err == APM_SUCCESS) ? 0 : -EIO;
12632ed8d2b3SRafael J. Wysocki
126419234c08SRafael J. Wysocki syscore_resume();
12659a163ed8SThomas Gleixner local_irq_enable();
12662ed8d2b3SRafael J. Wysocki
1267cf579dfbSRafael J. Wysocki dpm_resume_start(PMSG_RESUME);
1268d1616302SAlan Stern dpm_resume_end(PMSG_RESUME);
1269cf579dfbSRafael J. Wysocki
12709a163ed8SThomas Gleixner queue_event(APM_NORMAL_RESUME, NULL);
12719a163ed8SThomas Gleixner spin_lock(&user_list_lock);
12729a163ed8SThomas Gleixner for (as = user_list; as != NULL; as = as->next) {
12739a163ed8SThomas Gleixner as->suspend_wait = 0;
12749a163ed8SThomas Gleixner as->suspend_result = err;
12759a163ed8SThomas Gleixner }
12769a163ed8SThomas Gleixner spin_unlock(&user_list_lock);
12779a163ed8SThomas Gleixner wake_up_interruptible(&apm_suspend_waitqueue);
12789a163ed8SThomas Gleixner return err;
12799a163ed8SThomas Gleixner }
12809a163ed8SThomas Gleixner
standby(void)12819a163ed8SThomas Gleixner static void standby(void)
12829a163ed8SThomas Gleixner {
12839a163ed8SThomas Gleixner int err;
12849a163ed8SThomas Gleixner
1285cf579dfbSRafael J. Wysocki dpm_suspend_end(PMSG_SUSPEND);
12862ed8d2b3SRafael J. Wysocki
12872ed8d2b3SRafael J. Wysocki local_irq_disable();
128819234c08SRafael J. Wysocki syscore_suspend();
12899a163ed8SThomas Gleixner local_irq_enable();
12909a163ed8SThomas Gleixner
12919a163ed8SThomas Gleixner err = set_system_power_state(APM_STATE_STANDBY);
12929a163ed8SThomas Gleixner if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
12939a163ed8SThomas Gleixner apm_error("standby", err);
12949a163ed8SThomas Gleixner
12959a163ed8SThomas Gleixner local_irq_disable();
129619234c08SRafael J. Wysocki syscore_resume();
12979a163ed8SThomas Gleixner local_irq_enable();
12982ed8d2b3SRafael J. Wysocki
1299cf579dfbSRafael J. Wysocki dpm_resume_start(PMSG_RESUME);
13009a163ed8SThomas Gleixner }
13019a163ed8SThomas Gleixner
get_event(void)13029a163ed8SThomas Gleixner static apm_event_t get_event(void)
13039a163ed8SThomas Gleixner {
13049a163ed8SThomas Gleixner int error;
13059a163ed8SThomas Gleixner apm_event_t event = APM_NO_EVENTS; /* silence gcc */
13069a163ed8SThomas Gleixner apm_eventinfo_t info;
13079a163ed8SThomas Gleixner
13089a163ed8SThomas Gleixner static int notified;
13099a163ed8SThomas Gleixner
13109a163ed8SThomas Gleixner /* we don't use the eventinfo */
13119a163ed8SThomas Gleixner error = apm_get_event(&event, &info);
13129a163ed8SThomas Gleixner if (error == APM_SUCCESS)
13139a163ed8SThomas Gleixner return event;
13149a163ed8SThomas Gleixner
13159a163ed8SThomas Gleixner if ((error != APM_NO_EVENTS) && (notified++ == 0))
13169a163ed8SThomas Gleixner apm_error("get_event", error);
13179a163ed8SThomas Gleixner
13189a163ed8SThomas Gleixner return 0;
13199a163ed8SThomas Gleixner }
13209a163ed8SThomas Gleixner
check_events(void)13219a163ed8SThomas Gleixner static void check_events(void)
13229a163ed8SThomas Gleixner {
13239a163ed8SThomas Gleixner apm_event_t event;
13249a163ed8SThomas Gleixner static unsigned long last_resume;
13259a163ed8SThomas Gleixner static int ignore_bounce;
13269a163ed8SThomas Gleixner
13279a163ed8SThomas Gleixner while ((event = get_event()) != 0) {
13289a163ed8SThomas Gleixner if (debug) {
13299a163ed8SThomas Gleixner if (event <= NR_APM_EVENT_NAME)
13309a163ed8SThomas Gleixner printk(KERN_DEBUG "apm: received %s notify\n",
13319a163ed8SThomas Gleixner apm_event_name[event - 1]);
13329a163ed8SThomas Gleixner else
13339a163ed8SThomas Gleixner printk(KERN_DEBUG "apm: received unknown "
13349a163ed8SThomas Gleixner "event 0x%02x\n", event);
13359a163ed8SThomas Gleixner }
13369a163ed8SThomas Gleixner if (ignore_bounce
13374f2479f0SJulia Lawall && (time_after(jiffies, last_resume + bounce_interval)))
13389a163ed8SThomas Gleixner ignore_bounce = 0;
13399a163ed8SThomas Gleixner
13409a163ed8SThomas Gleixner switch (event) {
13419a163ed8SThomas Gleixner case APM_SYS_STANDBY:
13429a163ed8SThomas Gleixner case APM_USER_STANDBY:
13439a163ed8SThomas Gleixner queue_event(event, NULL);
13449a163ed8SThomas Gleixner if (standbys_pending <= 0)
13459a163ed8SThomas Gleixner standby();
13469a163ed8SThomas Gleixner break;
13479a163ed8SThomas Gleixner
13489a163ed8SThomas Gleixner case APM_USER_SUSPEND:
13499a163ed8SThomas Gleixner #ifdef CONFIG_APM_IGNORE_USER_SUSPEND
13509a163ed8SThomas Gleixner if (apm_info.connection_version > 0x100)
13519a163ed8SThomas Gleixner set_system_power_state(APM_STATE_REJECT);
13529a163ed8SThomas Gleixner break;
13539a163ed8SThomas Gleixner #endif
13549a163ed8SThomas Gleixner case APM_SYS_SUSPEND:
13559a163ed8SThomas Gleixner if (ignore_bounce) {
13569a163ed8SThomas Gleixner if (apm_info.connection_version > 0x100)
13579a163ed8SThomas Gleixner set_system_power_state(APM_STATE_REJECT);
13589a163ed8SThomas Gleixner break;
13599a163ed8SThomas Gleixner }
13609a163ed8SThomas Gleixner /*
13619a163ed8SThomas Gleixner * If we are already processing a SUSPEND,
13629a163ed8SThomas Gleixner * then further SUSPEND events from the BIOS
13639a163ed8SThomas Gleixner * will be ignored. We also return here to
13649a163ed8SThomas Gleixner * cope with the fact that the Thinkpads keep
13659a163ed8SThomas Gleixner * sending a SUSPEND event until something else
13669a163ed8SThomas Gleixner * happens!
13679a163ed8SThomas Gleixner */
13689a163ed8SThomas Gleixner if (ignore_sys_suspend)
13699a163ed8SThomas Gleixner return;
13709a163ed8SThomas Gleixner ignore_sys_suspend = 1;
13719a163ed8SThomas Gleixner queue_event(event, NULL);
13729a163ed8SThomas Gleixner if (suspends_pending <= 0)
13739a163ed8SThomas Gleixner (void) suspend(1);
13749a163ed8SThomas Gleixner break;
13759a163ed8SThomas Gleixner
13769a163ed8SThomas Gleixner case APM_NORMAL_RESUME:
13779a163ed8SThomas Gleixner case APM_CRITICAL_RESUME:
13789a163ed8SThomas Gleixner case APM_STANDBY_RESUME:
13799a163ed8SThomas Gleixner ignore_sys_suspend = 0;
13809a163ed8SThomas Gleixner last_resume = jiffies;
13819a163ed8SThomas Gleixner ignore_bounce = 1;
13829a163ed8SThomas Gleixner if ((event != APM_NORMAL_RESUME)
13839a163ed8SThomas Gleixner || (ignore_normal_resume == 0)) {
1384d1616302SAlan Stern dpm_resume_end(PMSG_RESUME);
13859a163ed8SThomas Gleixner queue_event(event, NULL);
13869a163ed8SThomas Gleixner }
13879a163ed8SThomas Gleixner ignore_normal_resume = 0;
13889a163ed8SThomas Gleixner break;
13899a163ed8SThomas Gleixner
13909a163ed8SThomas Gleixner case APM_CAPABILITY_CHANGE:
13919a163ed8SThomas Gleixner case APM_LOW_BATTERY:
13929a163ed8SThomas Gleixner case APM_POWER_STATUS_CHANGE:
13939a163ed8SThomas Gleixner queue_event(event, NULL);
13949a163ed8SThomas Gleixner /* If needed, notify drivers here */
13959a163ed8SThomas Gleixner break;
13969a163ed8SThomas Gleixner
13979a163ed8SThomas Gleixner case APM_UPDATE_TIME:
13989a163ed8SThomas Gleixner break;
13999a163ed8SThomas Gleixner
14009a163ed8SThomas Gleixner case APM_CRITICAL_SUSPEND:
14019a163ed8SThomas Gleixner /*
14029a163ed8SThomas Gleixner * We are not allowed to reject a critical suspend.
14039a163ed8SThomas Gleixner */
14049a163ed8SThomas Gleixner (void)suspend(0);
14059a163ed8SThomas Gleixner break;
14069a163ed8SThomas Gleixner }
14079a163ed8SThomas Gleixner }
14089a163ed8SThomas Gleixner }
14099a163ed8SThomas Gleixner
apm_event_handler(void)14109a163ed8SThomas Gleixner static void apm_event_handler(void)
14119a163ed8SThomas Gleixner {
14129a163ed8SThomas Gleixner static int pending_count = 4;
14139a163ed8SThomas Gleixner int err;
14149a163ed8SThomas Gleixner
14159a163ed8SThomas Gleixner if ((standbys_pending > 0) || (suspends_pending > 0)) {
14169a163ed8SThomas Gleixner if ((apm_info.connection_version > 0x100) &&
14179a163ed8SThomas Gleixner (pending_count-- <= 0)) {
14189a163ed8SThomas Gleixner pending_count = 4;
14199a163ed8SThomas Gleixner if (debug)
14209a163ed8SThomas Gleixner printk(KERN_DEBUG "apm: setting state busy\n");
14219a163ed8SThomas Gleixner err = set_system_power_state(APM_STATE_BUSY);
14229a163ed8SThomas Gleixner if (err)
14239a163ed8SThomas Gleixner apm_error("busy", err);
14249a163ed8SThomas Gleixner }
14259a163ed8SThomas Gleixner } else
14269a163ed8SThomas Gleixner pending_count = 4;
14279a163ed8SThomas Gleixner check_events();
14289a163ed8SThomas Gleixner }
14299a163ed8SThomas Gleixner
14309a163ed8SThomas Gleixner /*
14319a163ed8SThomas Gleixner * This is the APM thread main loop.
14329a163ed8SThomas Gleixner */
14339a163ed8SThomas Gleixner
apm_mainloop(void)14349a163ed8SThomas Gleixner static void apm_mainloop(void)
14359a163ed8SThomas Gleixner {
14369a163ed8SThomas Gleixner DECLARE_WAITQUEUE(wait, current);
14379a163ed8SThomas Gleixner
14389a163ed8SThomas Gleixner add_wait_queue(&apm_waitqueue, &wait);
14399a163ed8SThomas Gleixner set_current_state(TASK_INTERRUPTIBLE);
14409a163ed8SThomas Gleixner for (;;) {
14419a163ed8SThomas Gleixner schedule_timeout(APM_CHECK_TIMEOUT);
14429a163ed8SThomas Gleixner if (kthread_should_stop())
14439a163ed8SThomas Gleixner break;
14449a163ed8SThomas Gleixner /*
14459a163ed8SThomas Gleixner * Ok, check all events, check for idle (and mark us sleeping
14469a163ed8SThomas Gleixner * so as not to count towards the load average)..
14479a163ed8SThomas Gleixner */
14489a163ed8SThomas Gleixner set_current_state(TASK_INTERRUPTIBLE);
14499a163ed8SThomas Gleixner apm_event_handler();
14509a163ed8SThomas Gleixner }
14519a163ed8SThomas Gleixner remove_wait_queue(&apm_waitqueue, &wait);
14529a163ed8SThomas Gleixner }
14539a163ed8SThomas Gleixner
check_apm_user(struct apm_user * as,const char * func)14549a163ed8SThomas Gleixner static int check_apm_user(struct apm_user *as, const char *func)
14559a163ed8SThomas Gleixner {
1456b764a15fSAlan Cox if (as == NULL || as->magic != APM_BIOS_MAGIC) {
1457c767a54bSJoe Perches pr_err("%s passed bad filp\n", func);
14589a163ed8SThomas Gleixner return 1;
14599a163ed8SThomas Gleixner }
14609a163ed8SThomas Gleixner return 0;
14619a163ed8SThomas Gleixner }
14629a163ed8SThomas Gleixner
do_read(struct file * fp,char __user * buf,size_t count,loff_t * ppos)14639a163ed8SThomas Gleixner static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
14649a163ed8SThomas Gleixner {
14659a163ed8SThomas Gleixner struct apm_user *as;
14669a163ed8SThomas Gleixner int i;
14679a163ed8SThomas Gleixner apm_event_t event;
14689a163ed8SThomas Gleixner
14699a163ed8SThomas Gleixner as = fp->private_data;
14709a163ed8SThomas Gleixner if (check_apm_user(as, "read"))
14719a163ed8SThomas Gleixner return -EIO;
14729a163ed8SThomas Gleixner if ((int)count < sizeof(apm_event_t))
14739a163ed8SThomas Gleixner return -EINVAL;
14749a163ed8SThomas Gleixner if ((queue_empty(as)) && (fp->f_flags & O_NONBLOCK))
14759a163ed8SThomas Gleixner return -EAGAIN;
14769a163ed8SThomas Gleixner wait_event_interruptible(apm_waitqueue, !queue_empty(as));
14779a163ed8SThomas Gleixner i = count;
14789a163ed8SThomas Gleixner while ((i >= sizeof(event)) && !queue_empty(as)) {
14799a163ed8SThomas Gleixner event = get_queued_event(as);
14809a163ed8SThomas Gleixner if (copy_to_user(buf, &event, sizeof(event))) {
14819a163ed8SThomas Gleixner if (i < count)
14829a163ed8SThomas Gleixner break;
14839a163ed8SThomas Gleixner return -EFAULT;
14849a163ed8SThomas Gleixner }
14859a163ed8SThomas Gleixner switch (event) {
14869a163ed8SThomas Gleixner case APM_SYS_SUSPEND:
14879a163ed8SThomas Gleixner case APM_USER_SUSPEND:
14889a163ed8SThomas Gleixner as->suspends_read++;
14899a163ed8SThomas Gleixner break;
14909a163ed8SThomas Gleixner
14919a163ed8SThomas Gleixner case APM_SYS_STANDBY:
14929a163ed8SThomas Gleixner case APM_USER_STANDBY:
14939a163ed8SThomas Gleixner as->standbys_read++;
14949a163ed8SThomas Gleixner break;
14959a163ed8SThomas Gleixner }
14969a163ed8SThomas Gleixner buf += sizeof(event);
14979a163ed8SThomas Gleixner i -= sizeof(event);
14989a163ed8SThomas Gleixner }
14999a163ed8SThomas Gleixner if (i < count)
15009a163ed8SThomas Gleixner return count - i;
15019a163ed8SThomas Gleixner if (signal_pending(current))
15029a163ed8SThomas Gleixner return -ERESTARTSYS;
15039a163ed8SThomas Gleixner return 0;
15049a163ed8SThomas Gleixner }
15059a163ed8SThomas Gleixner
do_poll(struct file * fp,poll_table * wait)1506b146e2ceSAl Viro static __poll_t do_poll(struct file *fp, poll_table *wait)
15079a163ed8SThomas Gleixner {
15089a163ed8SThomas Gleixner struct apm_user *as;
15099a163ed8SThomas Gleixner
15109a163ed8SThomas Gleixner as = fp->private_data;
15119a163ed8SThomas Gleixner if (check_apm_user(as, "poll"))
15129a163ed8SThomas Gleixner return 0;
15139a163ed8SThomas Gleixner poll_wait(fp, &apm_waitqueue, wait);
15149a163ed8SThomas Gleixner if (!queue_empty(as))
1515a9a08845SLinus Torvalds return EPOLLIN | EPOLLRDNORM;
15169a163ed8SThomas Gleixner return 0;
15179a163ed8SThomas Gleixner }
15189a163ed8SThomas Gleixner
do_ioctl(struct file * filp,u_int cmd,u_long arg)1519b764a15fSAlan Cox static long do_ioctl(struct file *filp, u_int cmd, u_long arg)
15209a163ed8SThomas Gleixner {
15219a163ed8SThomas Gleixner struct apm_user *as;
1522b764a15fSAlan Cox int ret;
15239a163ed8SThomas Gleixner
15249a163ed8SThomas Gleixner as = filp->private_data;
15259a163ed8SThomas Gleixner if (check_apm_user(as, "ioctl"))
15269a163ed8SThomas Gleixner return -EIO;
1527b764a15fSAlan Cox if (!as->suser || !as->writer)
15289a163ed8SThomas Gleixner return -EPERM;
15299a163ed8SThomas Gleixner switch (cmd) {
15309a163ed8SThomas Gleixner case APM_IOC_STANDBY:
153105d86412SThomas Gleixner mutex_lock(&apm_mutex);
15329a163ed8SThomas Gleixner if (as->standbys_read > 0) {
15339a163ed8SThomas Gleixner as->standbys_read--;
15349a163ed8SThomas Gleixner as->standbys_pending--;
15359a163ed8SThomas Gleixner standbys_pending--;
15369a163ed8SThomas Gleixner } else
15379a163ed8SThomas Gleixner queue_event(APM_USER_STANDBY, as);
15389a163ed8SThomas Gleixner if (standbys_pending <= 0)
15399a163ed8SThomas Gleixner standby();
154005d86412SThomas Gleixner mutex_unlock(&apm_mutex);
15419a163ed8SThomas Gleixner break;
15429a163ed8SThomas Gleixner case APM_IOC_SUSPEND:
154305d86412SThomas Gleixner mutex_lock(&apm_mutex);
15449a163ed8SThomas Gleixner if (as->suspends_read > 0) {
15459a163ed8SThomas Gleixner as->suspends_read--;
15469a163ed8SThomas Gleixner as->suspends_pending--;
15479a163ed8SThomas Gleixner suspends_pending--;
15489a163ed8SThomas Gleixner } else
15499a163ed8SThomas Gleixner queue_event(APM_USER_SUSPEND, as);
15509a163ed8SThomas Gleixner if (suspends_pending <= 0) {
1551b764a15fSAlan Cox ret = suspend(1);
155205d86412SThomas Gleixner mutex_unlock(&apm_mutex);
15539a163ed8SThomas Gleixner } else {
15549a163ed8SThomas Gleixner as->suspend_wait = 1;
155505d86412SThomas Gleixner mutex_unlock(&apm_mutex);
15569a163ed8SThomas Gleixner wait_event_interruptible(apm_suspend_waitqueue,
15579a163ed8SThomas Gleixner as->suspend_wait == 0);
1558b764a15fSAlan Cox ret = as->suspend_result;
15599a163ed8SThomas Gleixner }
1560b764a15fSAlan Cox return ret;
15619a163ed8SThomas Gleixner default:
1562b764a15fSAlan Cox return -ENOTTY;
15639a163ed8SThomas Gleixner }
15649a163ed8SThomas Gleixner return 0;
15659a163ed8SThomas Gleixner }
15669a163ed8SThomas Gleixner
do_release(struct inode * inode,struct file * filp)15679a163ed8SThomas Gleixner static int do_release(struct inode *inode, struct file *filp)
15689a163ed8SThomas Gleixner {
15699a163ed8SThomas Gleixner struct apm_user *as;
15709a163ed8SThomas Gleixner
15719a163ed8SThomas Gleixner as = filp->private_data;
15729a163ed8SThomas Gleixner if (check_apm_user(as, "release"))
15739a163ed8SThomas Gleixner return 0;
15749a163ed8SThomas Gleixner filp->private_data = NULL;
15759a163ed8SThomas Gleixner if (as->standbys_pending > 0) {
15769a163ed8SThomas Gleixner standbys_pending -= as->standbys_pending;
15779a163ed8SThomas Gleixner if (standbys_pending <= 0)
15789a163ed8SThomas Gleixner standby();
15799a163ed8SThomas Gleixner }
15809a163ed8SThomas Gleixner if (as->suspends_pending > 0) {
15819a163ed8SThomas Gleixner suspends_pending -= as->suspends_pending;
15829a163ed8SThomas Gleixner if (suspends_pending <= 0)
15839a163ed8SThomas Gleixner (void) suspend(1);
15849a163ed8SThomas Gleixner }
15859a163ed8SThomas Gleixner spin_lock(&user_list_lock);
15869a163ed8SThomas Gleixner if (user_list == as)
15879a163ed8SThomas Gleixner user_list = as->next;
15889a163ed8SThomas Gleixner else {
15899a163ed8SThomas Gleixner struct apm_user *as1;
15909a163ed8SThomas Gleixner
15919a163ed8SThomas Gleixner for (as1 = user_list;
15929a163ed8SThomas Gleixner (as1 != NULL) && (as1->next != as);
15939a163ed8SThomas Gleixner as1 = as1->next)
15949a163ed8SThomas Gleixner ;
15959a163ed8SThomas Gleixner if (as1 == NULL)
1596c767a54bSJoe Perches pr_err("filp not in user list\n");
15979a163ed8SThomas Gleixner else
15989a163ed8SThomas Gleixner as1->next = as->next;
15999a163ed8SThomas Gleixner }
16009a163ed8SThomas Gleixner spin_unlock(&user_list_lock);
16019a163ed8SThomas Gleixner kfree(as);
16029a163ed8SThomas Gleixner return 0;
16039a163ed8SThomas Gleixner }
16049a163ed8SThomas Gleixner
do_open(struct inode * inode,struct file * filp)16059a163ed8SThomas Gleixner static int do_open(struct inode *inode, struct file *filp)
16069a163ed8SThomas Gleixner {
16079a163ed8SThomas Gleixner struct apm_user *as;
16089a163ed8SThomas Gleixner
16099a163ed8SThomas Gleixner as = kmalloc(sizeof(*as), GFP_KERNEL);
1610c767a54bSJoe Perches if (as == NULL)
16119a163ed8SThomas Gleixner return -ENOMEM;
1612c767a54bSJoe Perches
16139a163ed8SThomas Gleixner as->magic = APM_BIOS_MAGIC;
16149a163ed8SThomas Gleixner as->event_tail = as->event_head = 0;
16159a163ed8SThomas Gleixner as->suspends_pending = as->standbys_pending = 0;
16169a163ed8SThomas Gleixner as->suspends_read = as->standbys_read = 0;
16179a163ed8SThomas Gleixner /*
16189a163ed8SThomas Gleixner * XXX - this is a tiny bit broken, when we consider BSD
16199a163ed8SThomas Gleixner * process accounting. If the device is opened by root, we
16209a163ed8SThomas Gleixner * instantly flag that we used superuser privs. Who knows,
16219a163ed8SThomas Gleixner * we might close the device immediately without doing a
16229a163ed8SThomas Gleixner * privileged operation -- cevans
16239a163ed8SThomas Gleixner */
16249a163ed8SThomas Gleixner as->suser = capable(CAP_SYS_ADMIN);
16259a163ed8SThomas Gleixner as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
16269a163ed8SThomas Gleixner as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
16279a163ed8SThomas Gleixner spin_lock(&user_list_lock);
16289a163ed8SThomas Gleixner as->next = user_list;
16299a163ed8SThomas Gleixner user_list = as;
16309a163ed8SThomas Gleixner spin_unlock(&user_list_lock);
16319a163ed8SThomas Gleixner filp->private_data = as;
16329a163ed8SThomas Gleixner return 0;
16339a163ed8SThomas Gleixner }
16349a163ed8SThomas Gleixner
1635002b87d2SRandy Dunlap #ifdef CONFIG_PROC_FS
proc_apm_show(struct seq_file * m,void * v)16369a163ed8SThomas Gleixner static int proc_apm_show(struct seq_file *m, void *v)
16379a163ed8SThomas Gleixner {
16389a163ed8SThomas Gleixner unsigned short bx;
16399a163ed8SThomas Gleixner unsigned short cx;
16409a163ed8SThomas Gleixner unsigned short dx;
16419a163ed8SThomas Gleixner int error;
16429a163ed8SThomas Gleixner unsigned short ac_line_status = 0xff;
16439a163ed8SThomas Gleixner unsigned short battery_status = 0xff;
16449a163ed8SThomas Gleixner unsigned short battery_flag = 0xff;
16459a163ed8SThomas Gleixner int percentage = -1;
16469a163ed8SThomas Gleixner int time_units = -1;
16479a163ed8SThomas Gleixner char *units = "?";
16489a163ed8SThomas Gleixner
16499a163ed8SThomas Gleixner if ((num_online_cpus() == 1) &&
16509a163ed8SThomas Gleixner !(error = apm_get_power_status(&bx, &cx, &dx))) {
16519a163ed8SThomas Gleixner ac_line_status = (bx >> 8) & 0xff;
16529a163ed8SThomas Gleixner battery_status = bx & 0xff;
16539a163ed8SThomas Gleixner if ((cx & 0xff) != 0xff)
16549a163ed8SThomas Gleixner percentage = cx & 0xff;
16559a163ed8SThomas Gleixner
16569a163ed8SThomas Gleixner if (apm_info.connection_version > 0x100) {
16579a163ed8SThomas Gleixner battery_flag = (cx >> 8) & 0xff;
16589a163ed8SThomas Gleixner if (dx != 0xffff) {
16599a163ed8SThomas Gleixner units = (dx & 0x8000) ? "min" : "sec";
16609a163ed8SThomas Gleixner time_units = dx & 0x7fff;
16619a163ed8SThomas Gleixner }
16629a163ed8SThomas Gleixner }
16639a163ed8SThomas Gleixner }
16649a163ed8SThomas Gleixner /* Arguments, with symbols from linux/apm_bios.h. Information is
16659a163ed8SThomas Gleixner from the Get Power Status (0x0a) call unless otherwise noted.
16669a163ed8SThomas Gleixner
16679a163ed8SThomas Gleixner 0) Linux driver version (this will change if format changes)
16689a163ed8SThomas Gleixner 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
16699a163ed8SThomas Gleixner 2) APM flags from APM Installation Check (0x00):
16709a163ed8SThomas Gleixner bit 0: APM_16_BIT_SUPPORT
16719a163ed8SThomas Gleixner bit 1: APM_32_BIT_SUPPORT
16729a163ed8SThomas Gleixner bit 2: APM_IDLE_SLOWS_CLOCK
16739a163ed8SThomas Gleixner bit 3: APM_BIOS_DISABLED
16749a163ed8SThomas Gleixner bit 4: APM_BIOS_DISENGAGED
16759a163ed8SThomas Gleixner 3) AC line status
16769a163ed8SThomas Gleixner 0x00: Off-line
16779a163ed8SThomas Gleixner 0x01: On-line
16789a163ed8SThomas Gleixner 0x02: On backup power (BIOS >= 1.1 only)
16799a163ed8SThomas Gleixner 0xff: Unknown
16809a163ed8SThomas Gleixner 4) Battery status
16819a163ed8SThomas Gleixner 0x00: High
16829a163ed8SThomas Gleixner 0x01: Low
16839a163ed8SThomas Gleixner 0x02: Critical
16849a163ed8SThomas Gleixner 0x03: Charging
16859a163ed8SThomas Gleixner 0x04: Selected battery not present (BIOS >= 1.2 only)
16869a163ed8SThomas Gleixner 0xff: Unknown
16879a163ed8SThomas Gleixner 5) Battery flag
16889a163ed8SThomas Gleixner bit 0: High
16899a163ed8SThomas Gleixner bit 1: Low
16909a163ed8SThomas Gleixner bit 2: Critical
16919a163ed8SThomas Gleixner bit 3: Charging
16929a163ed8SThomas Gleixner bit 7: No system battery
16939a163ed8SThomas Gleixner 0xff: Unknown
16949a163ed8SThomas Gleixner 6) Remaining battery life (percentage of charge):
16959a163ed8SThomas Gleixner 0-100: valid
16969a163ed8SThomas Gleixner -1: Unknown
16979a163ed8SThomas Gleixner 7) Remaining battery life (time units):
16989a163ed8SThomas Gleixner Number of remaining minutes or seconds
16999a163ed8SThomas Gleixner -1: Unknown
17009a163ed8SThomas Gleixner 8) min = minutes; sec = seconds */
17019a163ed8SThomas Gleixner
17029a163ed8SThomas Gleixner seq_printf(m, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
17039a163ed8SThomas Gleixner driver_version,
17049a163ed8SThomas Gleixner (apm_info.bios.version >> 8) & 0xff,
17059a163ed8SThomas Gleixner apm_info.bios.version & 0xff,
17069a163ed8SThomas Gleixner apm_info.bios.flags,
17079a163ed8SThomas Gleixner ac_line_status,
17089a163ed8SThomas Gleixner battery_status,
17099a163ed8SThomas Gleixner battery_flag,
17109a163ed8SThomas Gleixner percentage,
17119a163ed8SThomas Gleixner time_units,
17129a163ed8SThomas Gleixner units);
17139a163ed8SThomas Gleixner return 0;
17149a163ed8SThomas Gleixner }
1715002b87d2SRandy Dunlap #endif
17169a163ed8SThomas Gleixner
apm(void * unused)17179a163ed8SThomas Gleixner static int apm(void *unused)
17189a163ed8SThomas Gleixner {
17199a163ed8SThomas Gleixner unsigned short bx;
17209a163ed8SThomas Gleixner unsigned short cx;
17219a163ed8SThomas Gleixner unsigned short dx;
17229a163ed8SThomas Gleixner int error;
17239a163ed8SThomas Gleixner char *power_stat;
17249a163ed8SThomas Gleixner char *bat_stat;
17259a163ed8SThomas Gleixner
17269a163ed8SThomas Gleixner /* 2002/08/01 - WT
17279a163ed8SThomas Gleixner * This is to avoid random crashes at boot time during initialization
17289a163ed8SThomas Gleixner * on SMP systems in case of "apm=power-off" mode. Seen on ASUS A7M266D.
17299a163ed8SThomas Gleixner * Some bioses don't like being called from CPU != 0.
17309a163ed8SThomas Gleixner * Method suggested by Ingo Molnar.
17319a163ed8SThomas Gleixner */
173289bd55d1SRusty Russell set_cpus_allowed_ptr(current, cpumask_of(0));
17339a163ed8SThomas Gleixner BUG_ON(smp_processor_id() != 0);
17349a163ed8SThomas Gleixner
17359a163ed8SThomas Gleixner if (apm_info.connection_version == 0) {
17369a163ed8SThomas Gleixner apm_info.connection_version = apm_info.bios.version;
17379a163ed8SThomas Gleixner if (apm_info.connection_version > 0x100) {
17389a163ed8SThomas Gleixner /*
17399a163ed8SThomas Gleixner * We only support BIOSs up to version 1.2
17409a163ed8SThomas Gleixner */
17419a163ed8SThomas Gleixner if (apm_info.connection_version > 0x0102)
17429a163ed8SThomas Gleixner apm_info.connection_version = 0x0102;
17439a163ed8SThomas Gleixner error = apm_driver_version(&apm_info.connection_version);
17449a163ed8SThomas Gleixner if (error != APM_SUCCESS) {
17459a163ed8SThomas Gleixner apm_error("driver version", error);
17469a163ed8SThomas Gleixner /* Fall back to an APM 1.0 connection. */
17479a163ed8SThomas Gleixner apm_info.connection_version = 0x100;
17489a163ed8SThomas Gleixner }
17499a163ed8SThomas Gleixner }
17509a163ed8SThomas Gleixner }
17519a163ed8SThomas Gleixner
17529a163ed8SThomas Gleixner if (debug)
17539a163ed8SThomas Gleixner printk(KERN_INFO "apm: Connection version %d.%d\n",
17549a163ed8SThomas Gleixner (apm_info.connection_version >> 8) & 0xff,
17559a163ed8SThomas Gleixner apm_info.connection_version & 0xff);
17569a163ed8SThomas Gleixner
17579a163ed8SThomas Gleixner #ifdef CONFIG_APM_DO_ENABLE
17589a163ed8SThomas Gleixner if (apm_info.bios.flags & APM_BIOS_DISABLED) {
17599a163ed8SThomas Gleixner /*
17609a163ed8SThomas Gleixner * This call causes my NEC UltraLite Versa 33/C to hang if it
17619a163ed8SThomas Gleixner * is booted with PM disabled but not in the docking station.
17629a163ed8SThomas Gleixner * Unfortunate ...
17639a163ed8SThomas Gleixner */
17649a163ed8SThomas Gleixner error = apm_enable_power_management(1);
17659a163ed8SThomas Gleixner if (error) {
17669a163ed8SThomas Gleixner apm_error("enable power management", error);
17679a163ed8SThomas Gleixner return -1;
17689a163ed8SThomas Gleixner }
17699a163ed8SThomas Gleixner }
17709a163ed8SThomas Gleixner #endif
17719a163ed8SThomas Gleixner
17729a163ed8SThomas Gleixner if ((apm_info.bios.flags & APM_BIOS_DISENGAGED)
17739a163ed8SThomas Gleixner && (apm_info.connection_version > 0x0100)) {
17749a163ed8SThomas Gleixner error = apm_engage_power_management(APM_DEVICE_ALL, 1);
17759a163ed8SThomas Gleixner if (error) {
17769a163ed8SThomas Gleixner apm_error("engage power management", error);
17779a163ed8SThomas Gleixner return -1;
17789a163ed8SThomas Gleixner }
17799a163ed8SThomas Gleixner }
17809a163ed8SThomas Gleixner
17819a163ed8SThomas Gleixner if (debug && (num_online_cpus() == 1 || smp)) {
17829a163ed8SThomas Gleixner error = apm_get_power_status(&bx, &cx, &dx);
17839a163ed8SThomas Gleixner if (error)
17849a163ed8SThomas Gleixner printk(KERN_INFO "apm: power status not available\n");
17859a163ed8SThomas Gleixner else {
17869a163ed8SThomas Gleixner switch ((bx >> 8) & 0xff) {
17873f4380a1SCyrill Gorcunov case 0:
17883f4380a1SCyrill Gorcunov power_stat = "off line";
17893f4380a1SCyrill Gorcunov break;
17903f4380a1SCyrill Gorcunov case 1:
17913f4380a1SCyrill Gorcunov power_stat = "on line";
17923f4380a1SCyrill Gorcunov break;
17933f4380a1SCyrill Gorcunov case 2:
17943f4380a1SCyrill Gorcunov power_stat = "on backup power";
17953f4380a1SCyrill Gorcunov break;
17963f4380a1SCyrill Gorcunov default:
17973f4380a1SCyrill Gorcunov power_stat = "unknown";
17983f4380a1SCyrill Gorcunov break;
17999a163ed8SThomas Gleixner }
18009a163ed8SThomas Gleixner switch (bx & 0xff) {
18013f4380a1SCyrill Gorcunov case 0:
18023f4380a1SCyrill Gorcunov bat_stat = "high";
18033f4380a1SCyrill Gorcunov break;
18043f4380a1SCyrill Gorcunov case 1:
18053f4380a1SCyrill Gorcunov bat_stat = "low";
18063f4380a1SCyrill Gorcunov break;
18073f4380a1SCyrill Gorcunov case 2:
18083f4380a1SCyrill Gorcunov bat_stat = "critical";
18093f4380a1SCyrill Gorcunov break;
18103f4380a1SCyrill Gorcunov case 3:
18113f4380a1SCyrill Gorcunov bat_stat = "charging";
18123f4380a1SCyrill Gorcunov break;
18133f4380a1SCyrill Gorcunov default:
18143f4380a1SCyrill Gorcunov bat_stat = "unknown";
18153f4380a1SCyrill Gorcunov break;
18169a163ed8SThomas Gleixner }
18179a163ed8SThomas Gleixner printk(KERN_INFO
18189a163ed8SThomas Gleixner "apm: AC %s, battery status %s, battery life ",
18199a163ed8SThomas Gleixner power_stat, bat_stat);
18209a163ed8SThomas Gleixner if ((cx & 0xff) == 0xff)
18219a163ed8SThomas Gleixner printk("unknown\n");
18229a163ed8SThomas Gleixner else
18239a163ed8SThomas Gleixner printk("%d%%\n", cx & 0xff);
18249a163ed8SThomas Gleixner if (apm_info.connection_version > 0x100) {
18259a163ed8SThomas Gleixner printk(KERN_INFO
18269a163ed8SThomas Gleixner "apm: battery flag 0x%02x, battery life ",
18279a163ed8SThomas Gleixner (cx >> 8) & 0xff);
18289a163ed8SThomas Gleixner if (dx == 0xffff)
18299a163ed8SThomas Gleixner printk("unknown\n");
18309a163ed8SThomas Gleixner else
18319a163ed8SThomas Gleixner printk("%d %s\n", dx & 0x7fff,
18329a163ed8SThomas Gleixner (dx & 0x8000) ?
18339a163ed8SThomas Gleixner "minutes" : "seconds");
18349a163ed8SThomas Gleixner }
18359a163ed8SThomas Gleixner }
18369a163ed8SThomas Gleixner }
18379a163ed8SThomas Gleixner
18389a163ed8SThomas Gleixner /* Install our power off handler.. */
18399a163ed8SThomas Gleixner if (power_off)
18409a163ed8SThomas Gleixner pm_power_off = apm_power_off;
18419a163ed8SThomas Gleixner
18429a163ed8SThomas Gleixner if (num_online_cpus() == 1 || smp) {
18439a163ed8SThomas Gleixner #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
18449a163ed8SThomas Gleixner console_blank_hook = apm_console_blank;
18459a163ed8SThomas Gleixner #endif
18469a163ed8SThomas Gleixner apm_mainloop();
18479a163ed8SThomas Gleixner #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
18489a163ed8SThomas Gleixner console_blank_hook = NULL;
18499a163ed8SThomas Gleixner #endif
18509a163ed8SThomas Gleixner }
18519a163ed8SThomas Gleixner
18529a163ed8SThomas Gleixner return 0;
18539a163ed8SThomas Gleixner }
18549a163ed8SThomas Gleixner
18559a163ed8SThomas Gleixner #ifndef MODULE
apm_setup(char * str)18569a163ed8SThomas Gleixner static int __init apm_setup(char *str)
18579a163ed8SThomas Gleixner {
18589a163ed8SThomas Gleixner int invert;
18599a163ed8SThomas Gleixner
18609a163ed8SThomas Gleixner while ((str != NULL) && (*str != '\0')) {
18619a163ed8SThomas Gleixner if (strncmp(str, "off", 3) == 0)
18629a163ed8SThomas Gleixner apm_disabled = 1;
18639a163ed8SThomas Gleixner if (strncmp(str, "on", 2) == 0)
18649a163ed8SThomas Gleixner apm_disabled = 0;
18659a163ed8SThomas Gleixner if ((strncmp(str, "bounce-interval=", 16) == 0) ||
18669a163ed8SThomas Gleixner (strncmp(str, "bounce_interval=", 16) == 0))
18679a163ed8SThomas Gleixner bounce_interval = simple_strtol(str + 16, NULL, 0);
18689a163ed8SThomas Gleixner if ((strncmp(str, "idle-threshold=", 15) == 0) ||
18699a163ed8SThomas Gleixner (strncmp(str, "idle_threshold=", 15) == 0))
18709a163ed8SThomas Gleixner idle_threshold = simple_strtol(str + 15, NULL, 0);
18719a163ed8SThomas Gleixner if ((strncmp(str, "idle-period=", 12) == 0) ||
18729a163ed8SThomas Gleixner (strncmp(str, "idle_period=", 12) == 0))
18739a163ed8SThomas Gleixner idle_period = simple_strtol(str + 12, NULL, 0);
18749a163ed8SThomas Gleixner invert = (strncmp(str, "no-", 3) == 0) ||
18759a163ed8SThomas Gleixner (strncmp(str, "no_", 3) == 0);
18769a163ed8SThomas Gleixner if (invert)
18779a163ed8SThomas Gleixner str += 3;
18789a163ed8SThomas Gleixner if (strncmp(str, "debug", 5) == 0)
18799a163ed8SThomas Gleixner debug = !invert;
18809a163ed8SThomas Gleixner if ((strncmp(str, "power-off", 9) == 0) ||
18819a163ed8SThomas Gleixner (strncmp(str, "power_off", 9) == 0))
18829a163ed8SThomas Gleixner power_off = !invert;
18833f4380a1SCyrill Gorcunov if (strncmp(str, "smp", 3) == 0) {
18849a163ed8SThomas Gleixner smp = !invert;
18859a163ed8SThomas Gleixner idle_threshold = 100;
18869a163ed8SThomas Gleixner }
18879a163ed8SThomas Gleixner if ((strncmp(str, "allow-ints", 10) == 0) ||
18889a163ed8SThomas Gleixner (strncmp(str, "allow_ints", 10) == 0))
18899a163ed8SThomas Gleixner apm_info.allow_ints = !invert;
18909a163ed8SThomas Gleixner if ((strncmp(str, "broken-psr", 10) == 0) ||
18919a163ed8SThomas Gleixner (strncmp(str, "broken_psr", 10) == 0))
18929a163ed8SThomas Gleixner apm_info.get_power_status_broken = !invert;
18939a163ed8SThomas Gleixner if ((strncmp(str, "realmode-power-off", 18) == 0) ||
18949a163ed8SThomas Gleixner (strncmp(str, "realmode_power_off", 18) == 0))
18959a163ed8SThomas Gleixner apm_info.realmode_power_off = !invert;
18969a163ed8SThomas Gleixner str = strchr(str, ',');
18979a163ed8SThomas Gleixner if (str != NULL)
18989a163ed8SThomas Gleixner str += strspn(str, ", \t");
18999a163ed8SThomas Gleixner }
19009a163ed8SThomas Gleixner return 1;
19019a163ed8SThomas Gleixner }
19029a163ed8SThomas Gleixner
19039a163ed8SThomas Gleixner __setup("apm=", apm_setup);
19049a163ed8SThomas Gleixner #endif
19059a163ed8SThomas Gleixner
19069a163ed8SThomas Gleixner static const struct file_operations apm_bios_fops = {
19079a163ed8SThomas Gleixner .owner = THIS_MODULE,
19089a163ed8SThomas Gleixner .read = do_read,
19099a163ed8SThomas Gleixner .poll = do_poll,
1910b764a15fSAlan Cox .unlocked_ioctl = do_ioctl,
19119a163ed8SThomas Gleixner .open = do_open,
19129a163ed8SThomas Gleixner .release = do_release,
19136038f373SArnd Bergmann .llseek = noop_llseek,
19149a163ed8SThomas Gleixner };
19159a163ed8SThomas Gleixner
19169a163ed8SThomas Gleixner static struct miscdevice apm_device = {
19179a163ed8SThomas Gleixner APM_MINOR_DEV,
19189a163ed8SThomas Gleixner "apm_bios",
19199a163ed8SThomas Gleixner &apm_bios_fops
19209a163ed8SThomas Gleixner };
19219a163ed8SThomas Gleixner
19229a163ed8SThomas Gleixner
19239a163ed8SThomas Gleixner /* Simple "print if true" callback */
print_if_true(const struct dmi_system_id * d)192419ad7ae4SLinus Torvalds static int __init print_if_true(const struct dmi_system_id *d)
19259a163ed8SThomas Gleixner {
19269a163ed8SThomas Gleixner printk("%s\n", d->ident);
19279a163ed8SThomas Gleixner return 0;
19289a163ed8SThomas Gleixner }
19299a163ed8SThomas Gleixner
19309a163ed8SThomas Gleixner /*
19319a163ed8SThomas Gleixner * Some Bioses enable the PS/2 mouse (touchpad) at resume, even if it was
19329a163ed8SThomas Gleixner * disabled before the suspend. Linux used to get terribly confused by that.
19339a163ed8SThomas Gleixner */
broken_ps2_resume(const struct dmi_system_id * d)193419ad7ae4SLinus Torvalds static int __init broken_ps2_resume(const struct dmi_system_id *d)
19359a163ed8SThomas Gleixner {
19363f4380a1SCyrill Gorcunov printk(KERN_INFO "%s machine detected. Mousepad Resume Bug "
19373f4380a1SCyrill Gorcunov "workaround hopefully not needed.\n", d->ident);
19389a163ed8SThomas Gleixner return 0;
19399a163ed8SThomas Gleixner }
19409a163ed8SThomas Gleixner
19419a163ed8SThomas Gleixner /* Some bioses have a broken protected mode poweroff and need to use realmode */
set_realmode_power_off(const struct dmi_system_id * d)194219ad7ae4SLinus Torvalds static int __init set_realmode_power_off(const struct dmi_system_id *d)
19439a163ed8SThomas Gleixner {
19449a163ed8SThomas Gleixner if (apm_info.realmode_power_off == 0) {
19459a163ed8SThomas Gleixner apm_info.realmode_power_off = 1;
19463f4380a1SCyrill Gorcunov printk(KERN_INFO "%s bios detected. "
19473f4380a1SCyrill Gorcunov "Using realmode poweroff only.\n", d->ident);
19489a163ed8SThomas Gleixner }
19499a163ed8SThomas Gleixner return 0;
19509a163ed8SThomas Gleixner }
19519a163ed8SThomas Gleixner
19529a163ed8SThomas Gleixner /* Some laptops require interrupts to be enabled during APM calls */
set_apm_ints(const struct dmi_system_id * d)195319ad7ae4SLinus Torvalds static int __init set_apm_ints(const struct dmi_system_id *d)
19549a163ed8SThomas Gleixner {
19559a163ed8SThomas Gleixner if (apm_info.allow_ints == 0) {
19569a163ed8SThomas Gleixner apm_info.allow_ints = 1;
19573f4380a1SCyrill Gorcunov printk(KERN_INFO "%s machine detected. "
19583f4380a1SCyrill Gorcunov "Enabling interrupts during APM calls.\n", d->ident);
19599a163ed8SThomas Gleixner }
19609a163ed8SThomas Gleixner return 0;
19619a163ed8SThomas Gleixner }
19629a163ed8SThomas Gleixner
19639a163ed8SThomas Gleixner /* Some APM bioses corrupt memory or just plain do not work */
apm_is_horked(const struct dmi_system_id * d)196419ad7ae4SLinus Torvalds static int __init apm_is_horked(const struct dmi_system_id *d)
19659a163ed8SThomas Gleixner {
19669a163ed8SThomas Gleixner if (apm_info.disabled == 0) {
19679a163ed8SThomas Gleixner apm_info.disabled = 1;
19683f4380a1SCyrill Gorcunov printk(KERN_INFO "%s machine detected. "
19693f4380a1SCyrill Gorcunov "Disabling APM.\n", d->ident);
19709a163ed8SThomas Gleixner }
19719a163ed8SThomas Gleixner return 0;
19729a163ed8SThomas Gleixner }
19739a163ed8SThomas Gleixner
apm_is_horked_d850md(const struct dmi_system_id * d)197419ad7ae4SLinus Torvalds static int __init apm_is_horked_d850md(const struct dmi_system_id *d)
19759a163ed8SThomas Gleixner {
19769a163ed8SThomas Gleixner if (apm_info.disabled == 0) {
19779a163ed8SThomas Gleixner apm_info.disabled = 1;
19783f4380a1SCyrill Gorcunov printk(KERN_INFO "%s machine detected. "
19793f4380a1SCyrill Gorcunov "Disabling APM.\n", d->ident);
19809a163ed8SThomas Gleixner printk(KERN_INFO "This bug is fixed in bios P15 which is available for\n");
19819a163ed8SThomas Gleixner printk(KERN_INFO "download from support.intel.com\n");
19829a163ed8SThomas Gleixner }
19839a163ed8SThomas Gleixner return 0;
19849a163ed8SThomas Gleixner }
19859a163ed8SThomas Gleixner
19869a163ed8SThomas Gleixner /* Some APM bioses hang on APM idle calls */
apm_likes_to_melt(const struct dmi_system_id * d)198719ad7ae4SLinus Torvalds static int __init apm_likes_to_melt(const struct dmi_system_id *d)
19889a163ed8SThomas Gleixner {
19899a163ed8SThomas Gleixner if (apm_info.forbid_idle == 0) {
19909a163ed8SThomas Gleixner apm_info.forbid_idle = 1;
19913f4380a1SCyrill Gorcunov printk(KERN_INFO "%s machine detected. "
19923f4380a1SCyrill Gorcunov "Disabling APM idle calls.\n", d->ident);
19939a163ed8SThomas Gleixner }
19949a163ed8SThomas Gleixner return 0;
19959a163ed8SThomas Gleixner }
19969a163ed8SThomas Gleixner
19979a163ed8SThomas Gleixner /*
19989a163ed8SThomas Gleixner * Check for clue free BIOS implementations who use
19999a163ed8SThomas Gleixner * the following QA technique
20009a163ed8SThomas Gleixner *
20019a163ed8SThomas Gleixner * [ Write BIOS Code ]<------
20029a163ed8SThomas Gleixner * | ^
20039a163ed8SThomas Gleixner * < Does it Compile >----N--
20049a163ed8SThomas Gleixner * |Y ^
20059a163ed8SThomas Gleixner * < Does it Boot Win98 >-N--
20069a163ed8SThomas Gleixner * |Y
20079a163ed8SThomas Gleixner * [Ship It]
20089a163ed8SThomas Gleixner *
20099a163ed8SThomas Gleixner * Phoenix A04 08/24/2000 is known bad (Dell Inspiron 5000e)
20109a163ed8SThomas Gleixner * Phoenix A07 09/29/2000 is known good (Dell Inspiron 5000)
20119a163ed8SThomas Gleixner */
broken_apm_power(const struct dmi_system_id * d)201219ad7ae4SLinus Torvalds static int __init broken_apm_power(const struct dmi_system_id *d)
20139a163ed8SThomas Gleixner {
20149a163ed8SThomas Gleixner apm_info.get_power_status_broken = 1;
20153f4380a1SCyrill Gorcunov printk(KERN_WARNING "BIOS strings suggest APM bugs, "
20163f4380a1SCyrill Gorcunov "disabling power status reporting.\n");
20179a163ed8SThomas Gleixner return 0;
20189a163ed8SThomas Gleixner }
20199a163ed8SThomas Gleixner
20209a163ed8SThomas Gleixner /*
20219a163ed8SThomas Gleixner * This bios swaps the APM minute reporting bytes over (Many sony laptops
20229a163ed8SThomas Gleixner * have this problem).
20239a163ed8SThomas Gleixner */
swab_apm_power_in_minutes(const struct dmi_system_id * d)202419ad7ae4SLinus Torvalds static int __init swab_apm_power_in_minutes(const struct dmi_system_id *d)
20259a163ed8SThomas Gleixner {
20269a163ed8SThomas Gleixner apm_info.get_power_status_swabinminutes = 1;
20273f4380a1SCyrill Gorcunov printk(KERN_WARNING "BIOS strings suggest APM reports battery life "
20283f4380a1SCyrill Gorcunov "in minutes and wrong byte order.\n");
20299a163ed8SThomas Gleixner return 0;
20309a163ed8SThomas Gleixner }
20319a163ed8SThomas Gleixner
20326faadbbbSChristoph Hellwig static const struct dmi_system_id apm_dmi_table[] __initconst = {
20339a163ed8SThomas Gleixner {
20349a163ed8SThomas Gleixner print_if_true,
20359a163ed8SThomas Gleixner KERN_WARNING "IBM T23 - BIOS 1.03b+ and controller firmware 1.02+ may be needed for Linux APM.",
20369a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "IBM"),
20379a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "1AET38WW (1.01b)"), },
20389a163ed8SThomas Gleixner },
20399a163ed8SThomas Gleixner { /* Handle problems with APM on the C600 */
20409a163ed8SThomas Gleixner broken_ps2_resume, "Dell Latitude C600",
20419a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Dell"),
20429a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Latitude C600"), },
20439a163ed8SThomas Gleixner },
20449a163ed8SThomas Gleixner { /* Allow interrupts during suspend on Dell Latitude laptops*/
20459a163ed8SThomas Gleixner set_apm_ints, "Dell Latitude",
20469a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
20479a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Latitude C510"), }
20489a163ed8SThomas Gleixner },
20499a163ed8SThomas Gleixner { /* APM crashes */
20509a163ed8SThomas Gleixner apm_is_horked, "Dell Inspiron 2500",
20519a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
20529a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 2500"),
20539a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
20549a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
20559a163ed8SThomas Gleixner },
20569a163ed8SThomas Gleixner { /* Allow interrupts during suspend on Dell Inspiron laptops*/
20579a163ed8SThomas Gleixner set_apm_ints, "Dell Inspiron", {
20589a163ed8SThomas Gleixner DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
20599a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 4000"), },
20609a163ed8SThomas Gleixner },
20619a163ed8SThomas Gleixner { /* Handle problems with APM on Inspiron 5000e */
20629a163ed8SThomas Gleixner broken_apm_power, "Dell Inspiron 5000e",
20639a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
20649a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A04"),
20659a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "08/24/2000"), },
20669a163ed8SThomas Gleixner },
20679a163ed8SThomas Gleixner { /* Handle problems with APM on Inspiron 2500 */
20689a163ed8SThomas Gleixner broken_apm_power, "Dell Inspiron 2500",
20699a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
20709a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A12"),
20719a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "02/04/2002"), },
20729a163ed8SThomas Gleixner },
20739a163ed8SThomas Gleixner { /* APM crashes */
20749a163ed8SThomas Gleixner apm_is_horked, "Dell Dimension 4100",
20759a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
20769a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "XPS-Z"),
20779a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
20789a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
20799a163ed8SThomas Gleixner },
20809a163ed8SThomas Gleixner { /* Allow interrupts during suspend on Compaq Laptops*/
20819a163ed8SThomas Gleixner set_apm_ints, "Compaq 12XL125",
20829a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
20839a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Compaq PC"),
20849a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
20859a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "4.06"), },
20869a163ed8SThomas Gleixner },
20879a163ed8SThomas Gleixner { /* Allow interrupts during APM or the clock goes slow */
20889a163ed8SThomas Gleixner set_apm_ints, "ASUSTeK",
20899a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
20909a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "L8400K series Notebook PC"), },
20919a163ed8SThomas Gleixner },
20929a163ed8SThomas Gleixner { /* APM blows on shutdown */
20939a163ed8SThomas Gleixner apm_is_horked, "ABIT KX7-333[R]",
20949a163ed8SThomas Gleixner { DMI_MATCH(DMI_BOARD_VENDOR, "ABIT"),
20959a163ed8SThomas Gleixner DMI_MATCH(DMI_BOARD_NAME, "VT8367-8233A (KX7-333[R])"), },
20969a163ed8SThomas Gleixner },
20979a163ed8SThomas Gleixner { /* APM crashes */
20989a163ed8SThomas Gleixner apm_is_horked, "Trigem Delhi3",
20999a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "TriGem Computer, Inc"),
21009a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Delhi3"), },
21019a163ed8SThomas Gleixner },
21029a163ed8SThomas Gleixner { /* APM crashes */
21039a163ed8SThomas Gleixner apm_is_horked, "Fujitsu-Siemens",
21049a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "hoenix/FUJITSU SIEMENS"),
21059a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "Version1.01"), },
21069a163ed8SThomas Gleixner },
21079a163ed8SThomas Gleixner { /* APM crashes */
21089a163ed8SThomas Gleixner apm_is_horked_d850md, "Intel D850MD",
21099a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
21109a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "MV85010A.86A.0016.P07.0201251536"), },
21119a163ed8SThomas Gleixner },
21129a163ed8SThomas Gleixner { /* APM crashes */
21139a163ed8SThomas Gleixner apm_is_horked, "Intel D810EMO",
21149a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
21159a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "MO81010A.86A.0008.P04.0004170800"), },
21169a163ed8SThomas Gleixner },
21179a163ed8SThomas Gleixner { /* APM crashes */
21189a163ed8SThomas Gleixner apm_is_horked, "Dell XPS-Z",
21199a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
21209a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A11"),
21219a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "XPS-Z"), },
21229a163ed8SThomas Gleixner },
21239a163ed8SThomas Gleixner { /* APM crashes */
21249a163ed8SThomas Gleixner apm_is_horked, "Sharp PC-PJ/AX",
21259a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "SHARP"),
21269a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "PC-PJ/AX"),
21279a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VENDOR, "SystemSoft"),
21289a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "Version R2.08"), },
21299a163ed8SThomas Gleixner },
21309a163ed8SThomas Gleixner { /* APM crashes */
21319a163ed8SThomas Gleixner apm_is_horked, "Dell Inspiron 2500",
21329a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
21339a163ed8SThomas Gleixner DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 2500"),
21349a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21359a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
21369a163ed8SThomas Gleixner },
21379a163ed8SThomas Gleixner { /* APM idle hangs */
21389a163ed8SThomas Gleixner apm_likes_to_melt, "Jabil AMD",
21399a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
21409a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "0AASNP06"), },
21419a163ed8SThomas Gleixner },
21429a163ed8SThomas Gleixner { /* APM idle hangs */
21439a163ed8SThomas Gleixner apm_likes_to_melt, "AMI Bios",
21449a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
21459a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "0AASNP05"), },
21469a163ed8SThomas Gleixner },
21479a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-N505X(DE) */
21489a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21499a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21509a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0206H"),
21519a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "08/23/99"), },
21529a163ed8SThomas Gleixner },
21539a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-N505VX */
21549a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21559a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21569a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "W2K06H0"),
21579a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "02/03/00"), },
21589a163ed8SThomas Gleixner },
21599a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-XG29 */
21609a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21619a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21629a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0117A0"),
21639a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "04/25/00"), },
21649a163ed8SThomas Gleixner },
21659a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z600NE */
21669a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21679a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21689a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0121Z1"),
21699a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "05/11/00"), },
21709a163ed8SThomas Gleixner },
21719a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z600NE */
21729a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21739a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21749a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "WME01Z1"),
21759a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "08/11/00"), },
21769a163ed8SThomas Gleixner },
21779a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z600LEK(DE) */
21789a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21799a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21809a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0206Z3"),
21819a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "12/25/00"), },
21829a163ed8SThomas Gleixner },
21839a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z505LS */
21849a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21859a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21869a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0203D0"),
21879a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "05/12/00"), },
21889a163ed8SThomas Gleixner },
21899a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z505LS */
21909a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21919a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21929a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0203Z3"),
21939a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "08/25/00"), },
21949a163ed8SThomas Gleixner },
21959a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-Z505LS (with updated BIOS) */
21969a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
21979a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
21989a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0209Z3"),
21999a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "05/12/01"), },
22009a163ed8SThomas Gleixner },
22019a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-F104K */
22029a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
22039a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
22049a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0204K2"),
22059a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "08/28/00"), },
22069a163ed8SThomas Gleixner },
22079a163ed8SThomas Gleixner
22089a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-C1VN/C1VE */
22099a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
22109a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
22119a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0208P1"),
22129a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "11/09/00"), },
22139a163ed8SThomas Gleixner },
22149a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-C1VE */
22159a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
22169a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
22179a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "R0204P1"),
22189a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "09/12/00"), },
22199a163ed8SThomas Gleixner },
22209a163ed8SThomas Gleixner { /* Handle problems with APM on Sony Vaio PCG-C1VE */
22219a163ed8SThomas Gleixner swab_apm_power_in_minutes, "Sony VAIO",
22229a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
22239a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "WXPO1Z3"),
22249a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "10/26/01"), },
22259a163ed8SThomas Gleixner },
22269a163ed8SThomas Gleixner { /* broken PM poweroff bios */
22279a163ed8SThomas Gleixner set_realmode_power_off, "Award Software v4.60 PGMA",
22289a163ed8SThomas Gleixner { DMI_MATCH(DMI_BIOS_VENDOR, "Award Software International, Inc."),
22299a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_VERSION, "4.60 PGMA"),
22309a163ed8SThomas Gleixner DMI_MATCH(DMI_BIOS_DATE, "134526184"), },
22319a163ed8SThomas Gleixner },
22329a163ed8SThomas Gleixner
22339a163ed8SThomas Gleixner /* Generic per vendor APM settings */
22349a163ed8SThomas Gleixner
22359a163ed8SThomas Gleixner { /* Allow interrupts during suspend on IBM laptops */
22369a163ed8SThomas Gleixner set_apm_ints, "IBM",
22379a163ed8SThomas Gleixner { DMI_MATCH(DMI_SYS_VENDOR, "IBM"), },
22389a163ed8SThomas Gleixner },
22399a163ed8SThomas Gleixner
22409a163ed8SThomas Gleixner { }
22419a163ed8SThomas Gleixner };
22429a163ed8SThomas Gleixner
22439a163ed8SThomas Gleixner /*
22449a163ed8SThomas Gleixner * Just start the APM thread. We do NOT want to do APM BIOS
22459a163ed8SThomas Gleixner * calls from anything but the APM thread, if for no other reason
22469a163ed8SThomas Gleixner * than the fact that we don't trust the APM BIOS. This way,
22479a163ed8SThomas Gleixner * most common APM BIOS problems that lead to protection errors
22489a163ed8SThomas Gleixner * etc will have at least some level of being contained...
22499a163ed8SThomas Gleixner *
22509a163ed8SThomas Gleixner * In short, if something bad happens, at least we have a choice
22519a163ed8SThomas Gleixner * of just killing the apm thread..
22529a163ed8SThomas Gleixner */
apm_init(void)22539a163ed8SThomas Gleixner static int __init apm_init(void)
22549a163ed8SThomas Gleixner {
22559a163ed8SThomas Gleixner struct desc_struct *gdt;
22569a163ed8SThomas Gleixner int err;
22579a163ed8SThomas Gleixner
22589a163ed8SThomas Gleixner dmi_check_system(apm_dmi_table);
22599a163ed8SThomas Gleixner
22608bc55f80SLuis R. Rodriguez if (apm_info.bios.version == 0 || machine_is_olpc()) {
22619a163ed8SThomas Gleixner printk(KERN_INFO "apm: BIOS not found.\n");
22629a163ed8SThomas Gleixner return -ENODEV;
22639a163ed8SThomas Gleixner }
22649a163ed8SThomas Gleixner printk(KERN_INFO
22659a163ed8SThomas Gleixner "apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n",
22669a163ed8SThomas Gleixner ((apm_info.bios.version >> 8) & 0xff),
22679a163ed8SThomas Gleixner (apm_info.bios.version & 0xff),
22689a163ed8SThomas Gleixner apm_info.bios.flags,
22699a163ed8SThomas Gleixner driver_version);
22709a163ed8SThomas Gleixner if ((apm_info.bios.flags & APM_32_BIT_SUPPORT) == 0) {
22719a163ed8SThomas Gleixner printk(KERN_INFO "apm: no 32 bit BIOS support\n");
22729a163ed8SThomas Gleixner return -ENODEV;
22739a163ed8SThomas Gleixner }
22749a163ed8SThomas Gleixner
22759a163ed8SThomas Gleixner if (allow_ints)
22769a163ed8SThomas Gleixner apm_info.allow_ints = 1;
22779a163ed8SThomas Gleixner if (broken_psr)
22789a163ed8SThomas Gleixner apm_info.get_power_status_broken = 1;
22799a163ed8SThomas Gleixner if (realmode_power_off)
22809a163ed8SThomas Gleixner apm_info.realmode_power_off = 1;
22819a163ed8SThomas Gleixner /* User can override, but default is to trust DMI */
22829a163ed8SThomas Gleixner if (apm_disabled != -1)
22839a163ed8SThomas Gleixner apm_info.disabled = apm_disabled;
22849a163ed8SThomas Gleixner
22859a163ed8SThomas Gleixner /*
22869a163ed8SThomas Gleixner * Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
22879a163ed8SThomas Gleixner * but is reportedly a 1.0 BIOS.
22889a163ed8SThomas Gleixner */
22899a163ed8SThomas Gleixner if (apm_info.bios.version == 0x001)
22909a163ed8SThomas Gleixner apm_info.bios.version = 0x100;
22919a163ed8SThomas Gleixner
22929a163ed8SThomas Gleixner /* BIOS < 1.2 doesn't set cseg_16_len */
22939a163ed8SThomas Gleixner if (apm_info.bios.version < 0x102)
22949a163ed8SThomas Gleixner apm_info.bios.cseg_16_len = 0; /* 64k */
22959a163ed8SThomas Gleixner
22969a163ed8SThomas Gleixner if (debug) {
22979a163ed8SThomas Gleixner printk(KERN_INFO "apm: entry %x:%x cseg16 %x dseg %x",
22989a163ed8SThomas Gleixner apm_info.bios.cseg, apm_info.bios.offset,
22999a163ed8SThomas Gleixner apm_info.bios.cseg_16, apm_info.bios.dseg);
23009a163ed8SThomas Gleixner if (apm_info.bios.version > 0x100)
23019a163ed8SThomas Gleixner printk(" cseg len %x, dseg len %x",
23029a163ed8SThomas Gleixner apm_info.bios.cseg_len,
23039a163ed8SThomas Gleixner apm_info.bios.dseg_len);
23049a163ed8SThomas Gleixner if (apm_info.bios.version > 0x101)
23059a163ed8SThomas Gleixner printk(" cseg16 len %x", apm_info.bios.cseg_16_len);
23069a163ed8SThomas Gleixner printk("\n");
23079a163ed8SThomas Gleixner }
23089a163ed8SThomas Gleixner
23099a163ed8SThomas Gleixner if (apm_info.disabled) {
2310c767a54bSJoe Perches pr_notice("disabled on user request.\n");
23119a163ed8SThomas Gleixner return -ENODEV;
23129a163ed8SThomas Gleixner }
23139a163ed8SThomas Gleixner if ((num_online_cpus() > 1) && !power_off && !smp) {
2314c767a54bSJoe Perches pr_notice("disabled - APM is not SMP safe.\n");
23159a163ed8SThomas Gleixner apm_info.disabled = 1;
23169a163ed8SThomas Gleixner return -ENODEV;
23179a163ed8SThomas Gleixner }
23186831c6edSRafael J. Wysocki if (!acpi_disabled) {
2319c767a54bSJoe Perches pr_notice("overridden by ACPI.\n");
23209a163ed8SThomas Gleixner apm_info.disabled = 1;
23219a163ed8SThomas Gleixner return -ENODEV;
23229a163ed8SThomas Gleixner }
23239a163ed8SThomas Gleixner
23249a163ed8SThomas Gleixner /*
23259a163ed8SThomas Gleixner * Set up the long jump entry point to the APM BIOS, which is called
23269a163ed8SThomas Gleixner * from inline assembly.
23279a163ed8SThomas Gleixner */
23289a163ed8SThomas Gleixner apm_bios_entry.offset = apm_info.bios.offset;
23299a163ed8SThomas Gleixner apm_bios_entry.segment = APM_CS;
23309a163ed8SThomas Gleixner
23319a163ed8SThomas Gleixner /*
23329a163ed8SThomas Gleixner * The APM 1.1 BIOS is supposed to provide limit information that it
23339a163ed8SThomas Gleixner * recognizes. Many machines do this correctly, but many others do
23349a163ed8SThomas Gleixner * not restrict themselves to their claimed limit. When this happens,
23359a163ed8SThomas Gleixner * they will cause a segmentation violation in the kernel at boot time.
23369a163ed8SThomas Gleixner * Most BIOS's, however, will respect a 64k limit, so we use that.
23379a163ed8SThomas Gleixner *
23389a163ed8SThomas Gleixner * Note we only set APM segments on CPU zero, since we pin the APM
23399a163ed8SThomas Gleixner * code to that CPU.
23409a163ed8SThomas Gleixner */
234169218e47SThomas Garnier gdt = get_cpu_gdt_rw(0);
234257594742SAkinobu Mita set_desc_base(&gdt[APM_CS >> 3],
234357594742SAkinobu Mita (unsigned long)__va((unsigned long)apm_info.bios.cseg << 4));
234457594742SAkinobu Mita set_desc_base(&gdt[APM_CS_16 >> 3],
234557594742SAkinobu Mita (unsigned long)__va((unsigned long)apm_info.bios.cseg_16 << 4));
234657594742SAkinobu Mita set_desc_base(&gdt[APM_DS >> 3],
234757594742SAkinobu Mita (unsigned long)__va((unsigned long)apm_info.bios.dseg << 4));
23489a163ed8SThomas Gleixner
23493f3942acSChristoph Hellwig proc_create_single("apm", 0, NULL, proc_apm_show);
23509a163ed8SThomas Gleixner
23519a163ed8SThomas Gleixner kapmd_task = kthread_create(apm, NULL, "kapmd");
23529a163ed8SThomas Gleixner if (IS_ERR(kapmd_task)) {
2353c767a54bSJoe Perches pr_err("disabled - Unable to start kernel thread\n");
23549a163ed8SThomas Gleixner err = PTR_ERR(kapmd_task);
23559a163ed8SThomas Gleixner kapmd_task = NULL;
23569a163ed8SThomas Gleixner remove_proc_entry("apm", NULL);
23579a163ed8SThomas Gleixner return err;
23589a163ed8SThomas Gleixner }
23599a163ed8SThomas Gleixner wake_up_process(kapmd_task);
23609a163ed8SThomas Gleixner
23619a163ed8SThomas Gleixner if (num_online_cpus() > 1 && !smp) {
23629a163ed8SThomas Gleixner printk(KERN_NOTICE
23639a163ed8SThomas Gleixner "apm: disabled - APM is not SMP safe (power off active).\n");
23649a163ed8SThomas Gleixner return 0;
23659a163ed8SThomas Gleixner }
23669a163ed8SThomas Gleixner
23679a163ed8SThomas Gleixner /*
23689a163ed8SThomas Gleixner * Note we don't actually care if the misc_device cannot be registered.
23699a163ed8SThomas Gleixner * this driver can do its job without it, even if userspace can't
23709a163ed8SThomas Gleixner * control it. just log the error
23719a163ed8SThomas Gleixner */
23729a163ed8SThomas Gleixner if (misc_register(&apm_device))
23739a163ed8SThomas Gleixner printk(KERN_WARNING "apm: Could not register misc device.\n");
23749a163ed8SThomas Gleixner
23759a163ed8SThomas Gleixner if (HZ != 100)
23769a163ed8SThomas Gleixner idle_period = (idle_period * HZ) / 100;
23779a163ed8SThomas Gleixner if (idle_threshold < 100) {
2378f8594220SRafael J. Wysocki cpuidle_poll_state_init(&apm_idle_driver);
2379dd8af076SLen Brown if (!cpuidle_register_driver(&apm_idle_driver))
2380dd8af076SLen Brown if (cpuidle_register_device(&apm_cpuidle_device))
2381dd8af076SLen Brown cpuidle_unregister_driver(&apm_idle_driver);
23829a163ed8SThomas Gleixner }
23839a163ed8SThomas Gleixner
23849a163ed8SThomas Gleixner return 0;
23859a163ed8SThomas Gleixner }
23869a163ed8SThomas Gleixner
apm_exit(void)23879a163ed8SThomas Gleixner static void __exit apm_exit(void)
23889a163ed8SThomas Gleixner {
23899a163ed8SThomas Gleixner int error;
23909a163ed8SThomas Gleixner
2391dd8af076SLen Brown cpuidle_unregister_device(&apm_cpuidle_device);
2392dd8af076SLen Brown cpuidle_unregister_driver(&apm_idle_driver);
2393dd8af076SLen Brown
23949a163ed8SThomas Gleixner if (((apm_info.bios.flags & APM_BIOS_DISENGAGED) == 0)
23959a163ed8SThomas Gleixner && (apm_info.connection_version > 0x0100)) {
23969a163ed8SThomas Gleixner error = apm_engage_power_management(APM_DEVICE_ALL, 0);
23979a163ed8SThomas Gleixner if (error)
23989a163ed8SThomas Gleixner apm_error("disengage power management", error);
23999a163ed8SThomas Gleixner }
24009a163ed8SThomas Gleixner misc_deregister(&apm_device);
24019a163ed8SThomas Gleixner remove_proc_entry("apm", NULL);
24029a163ed8SThomas Gleixner if (power_off)
24039a163ed8SThomas Gleixner pm_power_off = NULL;
24049a163ed8SThomas Gleixner if (kapmd_task) {
24059a163ed8SThomas Gleixner kthread_stop(kapmd_task);
24069a163ed8SThomas Gleixner kapmd_task = NULL;
24079a163ed8SThomas Gleixner }
24089a163ed8SThomas Gleixner }
24099a163ed8SThomas Gleixner
24109a163ed8SThomas Gleixner module_init(apm_init);
24119a163ed8SThomas Gleixner module_exit(apm_exit);
24129a163ed8SThomas Gleixner
24139a163ed8SThomas Gleixner MODULE_AUTHOR("Stephen Rothwell");
24149a163ed8SThomas Gleixner MODULE_DESCRIPTION("Advanced Power Management");
24159a163ed8SThomas Gleixner MODULE_LICENSE("GPL");
24169a163ed8SThomas Gleixner module_param(debug, bool, 0644);
24179a163ed8SThomas Gleixner MODULE_PARM_DESC(debug, "Enable debug mode");
24189a163ed8SThomas Gleixner module_param(power_off, bool, 0444);
24199a163ed8SThomas Gleixner MODULE_PARM_DESC(power_off, "Enable power off");
24209a163ed8SThomas Gleixner module_param(bounce_interval, int, 0444);
24219a163ed8SThomas Gleixner MODULE_PARM_DESC(bounce_interval,
24229a163ed8SThomas Gleixner "Set the number of ticks to ignore suspend bounces");
24239a163ed8SThomas Gleixner module_param(allow_ints, bool, 0444);
24249a163ed8SThomas Gleixner MODULE_PARM_DESC(allow_ints, "Allow interrupts during BIOS calls");
24259a163ed8SThomas Gleixner module_param(broken_psr, bool, 0444);
24269a163ed8SThomas Gleixner MODULE_PARM_DESC(broken_psr, "BIOS has a broken GetPowerStatus call");
24279a163ed8SThomas Gleixner module_param(realmode_power_off, bool, 0444);
24289a163ed8SThomas Gleixner MODULE_PARM_DESC(realmode_power_off,
24299a163ed8SThomas Gleixner "Switch to real mode before powering off");
24309a163ed8SThomas Gleixner module_param(idle_threshold, int, 0444);
24319a163ed8SThomas Gleixner MODULE_PARM_DESC(idle_threshold,
24329a163ed8SThomas Gleixner "System idle percentage above which to make APM BIOS idle calls");
24339a163ed8SThomas Gleixner module_param(idle_period, int, 0444);
24349a163ed8SThomas Gleixner MODULE_PARM_DESC(idle_period,
2435844ea8f6SColin Ian King "Period (in sec/100) over which to calculate the idle percentage");
24369a163ed8SThomas Gleixner module_param(smp, bool, 0444);
24379a163ed8SThomas Gleixner MODULE_PARM_DESC(smp,
24389a163ed8SThomas Gleixner "Set this to enable APM use on an SMP platform. Use with caution on older systems");
24399a163ed8SThomas Gleixner MODULE_ALIAS_MISCDEV(APM_MINOR_DEV);
2440