blob: e4ea362e84800a6c28bf8056ec340aff5fea4bad [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/* -*- linux-c -*-
2 * APM BIOS driver for Linux
3 * Copyright 1994-2001 Stephen Rothwell (sfr@canb.auug.org.au)
4 *
5 * Initial development of this driver was funded by NEC Australia P/L
6 * and NEC Corporation
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * October 1995, Rik Faith (faith@cs.unc.edu):
19 * Minor enhancements and updates (to the patch set) for 1.3.x
20 * Documentation
21 * January 1996, Rik Faith (faith@cs.unc.edu):
22 * Make /proc/apm easy to format (bump driver version)
23 * March 1996, Rik Faith (faith@cs.unc.edu):
24 * Prohibit APM BIOS calls unless apm_enabled.
25 * (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
26 * April 1996, Stephen Rothwell (sfr@canb.auug.org.au)
27 * Version 1.0 and 1.1
28 * May 1996, Version 1.2
29 * Feb 1998, Version 1.3
30 * Feb 1998, Version 1.4
31 * Aug 1998, Version 1.5
32 * Sep 1998, Version 1.6
33 * Nov 1998, Version 1.7
34 * Jan 1999, Version 1.8
35 * Jan 1999, Version 1.9
36 * Oct 1999, Version 1.10
37 * Nov 1999, Version 1.11
38 * Jan 2000, Version 1.12
39 * Feb 2000, Version 1.13
40 * Nov 2000, Version 1.14
41 * Oct 2001, Version 1.15
42 * Jan 2002, Version 1.16
43 * Oct 2002, Version 1.16ac
44 *
45 * History:
46 * 0.6b: first version in official kernel, Linux 1.3.46
47 * 0.7: changed /proc/apm format, Linux 1.3.58
48 * 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
49 * 0.9: only call bios if bios is present, Linux 1.3.72
50 * 1.0: use fixed device number, consolidate /proc/apm into this file,
51 * Linux 1.3.85
52 * 1.1: support user-space standby and suspend, power off after system
53 * halted, Linux 1.3.98
54 * 1.2: When resetting RTC after resume, take care so that the time
55 * is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth
56 * <jtoth@princeton.edu>); improve interaction between
57 * screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4
58 * 1.2a:Simple change to stop mysterious bug reports with SMP also added
59 * levels to the printk calls. APM is not defined for SMP machines.
Simon Arlott27b46d72007-10-20 01:13:56 +020060 * The new replacement for it is, but Linux doesn't yet support this.
Linus Torvalds1da177e2005-04-16 15:20:36 -070061 * Alan Cox Linux 2.1.55
62 * 1.3: Set up a valid data descriptor 0x40 for buggy BIOS's
63 * 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by
64 * Dean Gaudet <dgaudet@arctic.org>.
65 * C. Scott Ananian <cananian@alumni.princeton.edu> Linux 2.1.87
66 * 1.5: Fix segment register reloading (in case of bad segments saved
67 * across BIOS call).
68 * Stephen Rothwell
69 * 1.6: Cope with complier/assembler differences.
70 * Only try to turn off the first display device.
71 * Fix OOPS at power off with no APM BIOS by Jan Echternach
72 * <echter@informatik.uni-rostock.de>
73 * Stephen Rothwell
74 * 1.7: Modify driver's cached copy of the disabled/disengaged flags
75 * to reflect current state of APM BIOS.
76 * Chris Rankin <rankinc@bellsouth.net>
77 * Reset interrupt 0 timer to 100Hz after suspend
78 * Chad Miller <cmiller@surfsouth.com>
79 * Add CONFIG_APM_IGNORE_SUSPEND_BOUNCE
80 * Richard Gooch <rgooch@atnf.csiro.au>
81 * Allow boot time disabling of APM
82 * Make boot messages far less verbose by default
83 * Make asm safer
84 * Stephen Rothwell
85 * 1.8: Add CONFIG_APM_RTC_IS_GMT
86 * Richard Gooch <rgooch@atnf.csiro.au>
87 * change APM_NOINTS to CONFIG_APM_ALLOW_INTS
88 * remove dependency on CONFIG_PROC_FS
89 * Stephen Rothwell
90 * 1.9: Fix small typo. <laslo@wodip.opole.pl>
91 * Try to cope with BIOS's that need to have all display
92 * devices blanked and not just the first one.
93 * Ross Paterson <ross@soi.city.ac.uk>
94 * Fix segment limit setting it has always been wrong as
95 * the segments needed to have byte granularity.
96 * Mark a few things __init.
97 * Add hack to allow power off of SMP systems by popular request.
98 * Use CONFIG_SMP instead of __SMP__
99 * Ignore BOUNCES for three seconds.
100 * Stephen Rothwell
101 * 1.10: Fix for Thinkpad return code.
102 * Merge 2.2 and 2.3 drivers.
103 * Remove APM dependencies in arch/i386/kernel/process.c
104 * Remove APM dependencies in drivers/char/sysrq.c
105 * Reset time across standby.
106 * Allow more inititialisation on SMP.
107 * Remove CONFIG_APM_POWER_OFF and make it boot time
108 * configurable (default on).
109 * Make debug only a boot time parameter (remove APM_DEBUG).
110 * Try to blank all devices on any error.
111 * 1.11: Remove APM dependencies in drivers/char/console.c
112 * Check nr_running to detect if we are idle (from
113 * Borislav Deianov <borislav@lix.polytechnique.fr>)
114 * Fix for bioses that don't zero the top part of the
115 * entrypoint offset (Mario Sitta <sitta@al.unipmn.it>)
116 * (reported by Panos Katsaloulis <teras@writeme.com>).
117 * Real mode power off patch (Walter Hofmann
118 * <Walter.Hofmann@physik.stud.uni-erlangen.de>).
119 * 1.12: Remove CONFIG_SMP as the compiler will optimize
120 * the code away anyway (smp_num_cpus == 1 in UP)
121 * noted by Artur Skawina <skawina@geocities.com>.
122 * Make power off under SMP work again.
123 * Fix thinko with initial engaging of BIOS.
124 * Make sure power off only happens on CPU 0
125 * (Paul "Rusty" Russell <rusty@rustcorp.com.au>).
126 * Do error notification to user mode if BIOS calls fail.
127 * Move entrypoint offset fix to ...boot/setup.S
128 * where it belongs (Cosmos <gis88564@cis.nctu.edu.tw>).
129 * Remove smp-power-off. SMP users must now specify
130 * "apm=power-off" on the kernel command line. Suggested
131 * by Jim Avera <jima@hal.com>, modified by Alan Cox
132 * <alan@lxorguk.ukuu.org.uk>.
133 * Register the /proc/apm entry even on SMP so that
134 * scripts that check for it before doing power off
135 * work (Jim Avera <jima@hal.com>).
136 * 1.13: Changes for new pm_ interfaces (Andy Henroid
137 * <andy_henroid@yahoo.com>).
138 * Modularize the code.
139 * Fix the Thinkpad (again) :-( (CONFIG_APM_IGNORE_MULTIPLE_SUSPENDS
140 * is now the way life works).
141 * Fix thinko in suspend() (wrong return).
142 * Notify drivers on critical suspend.
143 * Make kapmd absorb more idle time (Pavel Machek <pavel@suse.cz>
144 * modified by sfr).
145 * Disable interrupts while we are suspended (Andy Henroid
146 * <andy_henroid@yahoo.com> fixed by sfr).
147 * Make power off work on SMP again (Tony Hoyle
148 * <tmh@magenta-logic.com> and <zlatko@iskon.hr>) modified by sfr.
149 * Remove CONFIG_APM_SUSPEND_BOUNCE. The bounce ignore
150 * interval is now configurable.
151 * 1.14: Make connection version persist across module unload/load.
152 * Enable and engage power management earlier.
153 * Disengage power management on module unload.
154 * Changed to use the sysrq-register hack for registering the
155 * power off function called by magic sysrq based upon discussions
156 * in irc://irc.openprojects.net/#kernelnewbies
157 * (Crutcher Dunnavant <crutcher+kernel@datastacks.com>).
158 * Make CONFIG_APM_REAL_MODE_POWER_OFF run time configurable.
159 * (Arjan van de Ven <arjanv@redhat.com>) modified by sfr.
160 * Work around byte swap bug in one of the Vaio's BIOS's
161 * (Marc Boucher <marc@mbsi.ca>).
162 * Exposed the disable flag to dmi so that we can handle known
163 * broken APM (Alan Cox <alan@redhat.com>).
164 * 1.14ac: If the BIOS says "I slowed the CPU down" then don't spin
165 * calling it - instead idle. (Alan Cox <alan@redhat.com>)
166 * If an APM idle fails log it and idle sensibly
167 * 1.15: Don't queue events to clients who open the device O_WRONLY.
168 * Don't expect replies from clients who open the device O_RDONLY.
169 * (Idea from Thomas Hood)
170 * Minor waitqueue cleanups. (John Fremlin <chief@bandits.org>)
171 * 1.16: Fix idle calling. (Andreas Steinmetz <ast@domdv.de> et al.)
172 * Notify listeners of standby or suspend events before notifying
173 * drivers. Return EBUSY to ioctl() if suspend is rejected.
174 * (Russell King <rmk@arm.linux.org.uk> and Thomas Hood)
175 * Ignore first resume after we generate our own resume event
176 * after a suspend (Thomas Hood)
177 * Daemonize now gets rid of our controlling terminal (sfr).
178 * CONFIG_APM_CPU_IDLE now just affects the default value of
179 * idle_threshold (sfr).
180 * Change name of kernel apm daemon (as it no longer idles) (sfr).
181 * 1.16ac: Fix up SMP support somewhat. You can now force SMP on and we
182 * make _all_ APM calls on the CPU#0. Fix unsafe sign bug.
183 * TODO: determine if its "boot CPU" or "CPU0" we want to lock to.
184 *
185 * APM 1.1 Reference:
186 *
187 * Intel Corporation, Microsoft Corporation. Advanced Power Management
188 * (APM) BIOS Interface Specification, Revision 1.1, September 1993.
189 * Intel Order Number 241704-001. Microsoft Part Number 781-110-X01.
190 *
191 * [This document is available free from Intel by calling 800.628.8686 (fax
192 * 916.356.6100) or 800.548.4725; or via anonymous ftp from
193 * ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc. It is also
194 * available from Microsoft by calling 206.882.8080.]
195 *
196 * APM 1.2 Reference:
197 * Intel Corporation, Microsoft Corporation. Advanced Power Management
198 * (APM) BIOS Interface Specification, Revision 1.2, February 1996.
199 *
200 * [This document is available from Microsoft at:
Kristian Mueller3f4b23e2006-10-29 22:46:46 -0800201 * http://www.microsoft.com/whdc/archive/amp_12.mspx]
Linus Torvalds1da177e2005-04-16 15:20:36 -0700202 */
203
Linus Torvalds1da177e2005-04-16 15:20:36 -0700204#include <linux/module.h>
205
206#include <linux/poll.h>
207#include <linux/types.h>
208#include <linux/stddef.h>
209#include <linux/timer.h>
210#include <linux/fcntl.h>
211#include <linux/slab.h>
212#include <linux/stat.h>
213#include <linux/proc_fs.h>
Alexey Dobriyan016d6f32007-02-13 13:26:23 +0100214#include <linux/seq_file.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215#include <linux/miscdevice.h>
216#include <linux/apm_bios.h>
217#include <linux/init.h>
218#include <linux/time.h>
219#include <linux/sched.h>
220#include <linux/pm.h>
Jeff Garzikbca73e42005-11-13 16:06:25 -0800221#include <linux/pm_legacy.h>
Randy Dunlapa9415642006-01-11 12:17:48 -0800222#include <linux/capability.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223#include <linux/device.h>
224#include <linux/kernel.h>
Rafael J. Wysocki83144182007-07-17 04:03:35 -0700225#include <linux/freezer.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226#include <linux/smp.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700227#include <linux/dmi.h>
228#include <linux/suspend.h>
Serge E. Hallynfc095612006-09-29 02:00:04 -0700229#include <linux/kthread.h>
Julia Lawall4f2479f2008-01-30 13:32:18 +0100230#include <linux/jiffies.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700231
232#include <asm/system.h>
233#include <asm/uaccess.h>
234#include <asm/desc.h>
Ingo Molnar306e4402005-06-30 02:58:55 -0700235#include <asm/i8253.h>
Rusty Russell6020c8f2006-12-07 02:14:08 +0100236#include <asm/paravirt.h>
Jeremy Fitzhardinge07f33312007-05-02 19:27:11 +0200237#include <asm/reboot.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238
Linus Torvalds1da177e2005-04-16 15:20:36 -0700239#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
240extern int (*console_blank_hook)(int);
241#endif
242
243/*
244 * The apm_bios device is one of the misc char devices.
245 * This is its minor number.
246 */
247#define APM_MINOR_DEV 134
248
249/*
250 * See Documentation/Config.help for the configuration options.
251 *
252 * Various options can be changed at boot time as follows:
253 * (We allow underscores for compatibility with the modules code)
254 * apm=on/off enable/disable APM
255 * [no-]allow[-_]ints allow interrupts during BIOS calls
256 * [no-]broken[-_]psr BIOS has a broken GetPowerStatus call
257 * [no-]realmode[-_]power[-_]off switch to real mode before
258 * powering off
259 * [no-]debug log some debugging messages
260 * [no-]power[-_]off power off on shutdown
261 * [no-]smp Use apm even on an SMP box
262 * bounce[-_]interval=<n> number of ticks to ignore suspend
263 * bounces
264 * idle[-_]threshold=<n> System idle percentage above which to
265 * make APM BIOS idle calls. Set it to
266 * 100 to disable.
267 * idle[-_]period=<n> Period (in 1/100s of a second) over
268 * which the idle percentage is
269 * calculated.
270 */
271
272/* KNOWN PROBLEM MACHINES:
273 *
274 * U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
275 * [Confirmed by TI representative]
276 * ?: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
277 * [Confirmed by BIOS disassembly]
278 * [This may work now ...]
279 * P: Toshiba 1950S: battery life information only gets updated after resume
280 * P: Midwest Micro Soundbook Elite DX2/66 monochrome: screen blanking
281 * broken in BIOS [Reported by Garst R. Reese <reese@isn.net>]
282 * ?: AcerNote-950: oops on reading /proc/apm - workaround is a WIP
283 * Neale Banks <neale@lowendale.com.au> December 2000
284 *
285 * Legend: U = unusable with APM patches
286 * P = partially usable with APM patches
287 */
288
289/*
290 * Define as 1 to make the driver always call the APM BIOS busy
291 * routine even if the clock was not reported as slowed by the
292 * idle routine. Otherwise, define as 0.
293 */
294#define ALWAYS_CALL_BUSY 1
295
296/*
297 * Define to make the APM BIOS calls zero all data segment registers (so
298 * that an incorrect BIOS implementation will cause a kernel panic if it
299 * tries to write to arbitrary memory).
300 */
301#define APM_ZERO_SEGS
302
303#include "apm.h"
304
305/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700306 * Define to re-initialize the interrupt 0 timer to 100 Hz after a suspend.
307 * This patched by Chad Miller <cmiller@surfsouth.com>, original code by
308 * David Chen <chen@ctpa04.mit.edu>
309 */
310#undef INIT_TIMER_AFTER_SUSPEND
311
312#ifdef INIT_TIMER_AFTER_SUSPEND
313#include <linux/timex.h>
314#include <asm/io.h>
315#include <linux/delay.h>
316#endif
317
318/*
319 * Need to poll the APM BIOS every second
320 */
321#define APM_CHECK_TIMEOUT (HZ)
322
323/*
324 * Ignore suspend events for this amount of time after a resume
325 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100326#define DEFAULT_BOUNCE_INTERVAL (3 * HZ)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700327
328/*
329 * Maximum number of events stored
330 */
331#define APM_MAX_EVENTS 20
332
333/*
334 * The per-file APM data
335 */
336struct apm_user {
337 int magic;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100338 struct apm_user *next;
Domen Puncer77617bd2005-06-25 14:58:45 -0700339 unsigned int suser: 1;
340 unsigned int writer: 1;
341 unsigned int reader: 1;
342 unsigned int suspend_wait: 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700343 int suspend_result;
344 int suspends_pending;
345 int standbys_pending;
346 int suspends_read;
347 int standbys_read;
348 int event_head;
349 int event_tail;
350 apm_event_t events[APM_MAX_EVENTS];
351};
352
353/*
354 * The magic number in apm_user
355 */
356#define APM_BIOS_MAGIC 0x4101
357
358/*
359 * idle percentage above which bios idle calls are done
360 */
361#ifdef CONFIG_APM_CPU_IDLE
362#define DEFAULT_IDLE_THRESHOLD 95
363#else
364#define DEFAULT_IDLE_THRESHOLD 100
365#endif
366#define DEFAULT_IDLE_PERIOD (100 / 3)
367
368/*
369 * Local variables
370 */
371static struct {
372 unsigned long offset;
373 unsigned short segment;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100374} apm_bios_entry;
375static int clock_slowed;
376static int idle_threshold __read_mostly = DEFAULT_IDLE_THRESHOLD;
377static int idle_period __read_mostly = DEFAULT_IDLE_PERIOD;
378static int set_pm_idle;
379static int suspends_pending;
380static int standbys_pending;
381static int ignore_sys_suspend;
382static int ignore_normal_resume;
383static int bounce_interval __read_mostly = DEFAULT_BOUNCE_INTERVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700384
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100385static int debug __read_mostly;
386static int smp __read_mostly;
387static int apm_disabled = -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700388#ifdef CONFIG_SMP
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100389static int power_off;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700390#else
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100391static int power_off = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700392#endif
393#ifdef CONFIG_APM_REAL_MODE_POWER_OFF
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100394static int realmode_power_off = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700395#else
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100396static int realmode_power_off;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700397#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700398#ifdef CONFIG_APM_ALLOW_INTS
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100399static int allow_ints = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700400#else
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100401static int allow_ints;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700402#endif
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100403static int broken_psr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700404
405static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
406static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100407static struct apm_user *user_list;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700408static DEFINE_SPINLOCK(user_list_lock);
Glauber de Oliveira Costa6842ef02008-01-30 13:31:11 +0100409static const struct desc_struct bad_bios_desc = { { { 0, 0x00409200 } } };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700410
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100411static const char driver_version[] = "1.16ac"; /* no spaces */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700412
Serge E. Hallynfc095612006-09-29 02:00:04 -0700413static struct task_struct *kapmd_task;
414
Linus Torvalds1da177e2005-04-16 15:20:36 -0700415/*
416 * APM event names taken from the APM 1.2 specification. These are
417 * the message codes that the BIOS uses to tell us about events
418 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100419static const char * const apm_event_name[] = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700420 "system standby",
421 "system suspend",
422 "normal resume",
423 "critical resume",
424 "low battery",
425 "power status change",
426 "update time",
427 "critical suspend",
428 "user standby",
429 "user suspend",
430 "system standby resume",
431 "capabilities change"
432};
Tobias Klauser38e548e2005-11-07 00:58:31 -0800433#define NR_APM_EVENT_NAME ARRAY_SIZE(apm_event_name)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700434
435typedef struct lookup_t {
436 int key;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100437 char *msg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700438} lookup_t;
439
440/*
441 * The BIOS returns a set of standard error codes in AX when the
442 * carry flag is set.
443 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100444
Linus Torvalds1da177e2005-04-16 15:20:36 -0700445static const lookup_t error_table[] = {
446/* N/A { APM_SUCCESS, "Operation succeeded" }, */
447 { APM_DISABLED, "Power management disabled" },
448 { APM_CONNECTED, "Real mode interface already connected" },
449 { APM_NOT_CONNECTED, "Interface not connected" },
450 { APM_16_CONNECTED, "16 bit interface already connected" },
451/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
452 { APM_32_CONNECTED, "32 bit interface already connected" },
453 { APM_32_UNSUPPORTED, "32 bit interface not supported" },
454 { APM_BAD_DEVICE, "Unrecognized device ID" },
455 { APM_BAD_PARAM, "Parameter out of range" },
456 { APM_NOT_ENGAGED, "Interface not engaged" },
457 { APM_BAD_FUNCTION, "Function not supported" },
458 { APM_RESUME_DISABLED, "Resume timer disabled" },
459 { APM_BAD_STATE, "Unable to enter requested state" },
460/* N/A { APM_NO_EVENTS, "No events pending" }, */
461 { APM_NO_ERROR, "BIOS did not set a return code" },
462 { APM_NOT_PRESENT, "No APM present" }
463};
Tobias Klauser38e548e2005-11-07 00:58:31 -0800464#define ERROR_COUNT ARRAY_SIZE(error_table)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700465
466/**
467 * apm_error - display an APM error
468 * @str: information string
469 * @err: APM BIOS return code
470 *
471 * Write a meaningful log entry to the kernel log in the event of
472 * an APM error.
473 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100474
Linus Torvalds1da177e2005-04-16 15:20:36 -0700475static void apm_error(char *str, int err)
476{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100477 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700478
479 for (i = 0; i < ERROR_COUNT; i++)
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100480 if (error_table[i].key == err)
481 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482 if (i < ERROR_COUNT)
483 printk(KERN_NOTICE "apm: %s: %s\n", str, error_table[i].msg);
484 else
485 printk(KERN_NOTICE "apm: %s: unknown error code %#2.2x\n",
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100486 str, err);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700487}
488
489/*
490 * Lock APM functionality to physical CPU 0
491 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100492
Linus Torvalds1da177e2005-04-16 15:20:36 -0700493#ifdef CONFIG_SMP
494
495static cpumask_t apm_save_cpus(void)
496{
497 cpumask_t x = current->cpus_allowed;
498 /* Some bioses don't like being called from CPU != 0 */
499 set_cpus_allowed(current, cpumask_of_cpu(0));
500 BUG_ON(smp_processor_id() != 0);
501 return x;
502}
503
504static inline void apm_restore_cpus(cpumask_t mask)
505{
506 set_cpus_allowed(current, mask);
507}
508
509#else
510
511/*
512 * No CPU lockdown needed on a uniprocessor
513 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100514
Linus Torvalds1da177e2005-04-16 15:20:36 -0700515#define apm_save_cpus() (current->cpus_allowed)
516#define apm_restore_cpus(x) (void)(x)
517
518#endif
519
520/*
521 * These are the actual BIOS calls. Depending on APM_ZERO_SEGS and
522 * apm_info.allow_ints, we are being really paranoid here! Not only
523 * are interrupts disabled, but all the segment registers (except SS)
524 * are saved and zeroed this means that if the BIOS tries to reference
525 * any data without explicitly loading the segment registers, the kernel
526 * will fault immediately rather than have some unforeseen circumstances
527 * for the rest of the kernel. And it will be very obvious! :-) Doing
528 * this depends on CS referring to the same physical memory as DS so that
529 * DS can be zeroed before the call. Unfortunately, we can't do anything
530 * about the stack segment/pointer. Also, we tell the compiler that
531 * everything could change.
532 *
533 * Also, we KNOW that for the non error case of apm_bios_call, there
534 * is no useful data returned in the low order 8 bits of eax.
535 */
Peter Zijlstra3864c482006-10-17 00:10:26 -0700536
537static inline unsigned long __apm_irq_save(void)
538{
539 unsigned long flags;
540 local_save_flags(flags);
541 if (apm_info.allow_ints) {
542 if (irqs_disabled_flags(flags))
543 local_irq_enable();
544 } else
Linus Torvalds1da177e2005-04-16 15:20:36 -0700545 local_irq_disable();
546
Peter Zijlstra3864c482006-10-17 00:10:26 -0700547 return flags;
548}
549
550#define apm_irq_save(flags) \
551 do { flags = __apm_irq_save(); } while (0)
552
553static inline void apm_irq_restore(unsigned long flags)
554{
555 if (irqs_disabled_flags(flags))
556 local_irq_disable();
557 else if (irqs_disabled())
558 local_irq_enable();
559}
560
Linus Torvalds1da177e2005-04-16 15:20:36 -0700561#ifdef APM_ZERO_SEGS
562# define APM_DECL_SEGS \
563 unsigned int saved_fs; unsigned int saved_gs;
564# define APM_DO_SAVE_SEGS \
565 savesegment(fs, saved_fs); savesegment(gs, saved_gs)
566# define APM_DO_RESTORE_SEGS \
567 loadsegment(fs, saved_fs); loadsegment(gs, saved_gs)
568#else
569# define APM_DECL_SEGS
570# define APM_DO_SAVE_SEGS
571# define APM_DO_RESTORE_SEGS
572#endif
573
574/**
575 * apm_bios_call - Make an APM BIOS 32bit call
576 * @func: APM function to execute
577 * @ebx_in: EBX register for call entry
578 * @ecx_in: ECX register for call entry
579 * @eax: EAX register return
580 * @ebx: EBX register return
581 * @ecx: ECX register return
582 * @edx: EDX register return
583 * @esi: ESI register return
584 *
585 * Make an APM call using the 32bit protected mode interface. The
586 * caller is responsible for knowing if APM BIOS is configured and
587 * enabled. This call can disable interrupts for a long period of
588 * time on some laptops. The return value is in AH and the carry
589 * flag is loaded into AL. If there is an error, then the error
590 * code is returned in AH (bits 8-15 of eax) and this function
591 * returns non-zero.
592 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100593
Linus Torvalds1da177e2005-04-16 15:20:36 -0700594static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in,
595 u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
596{
597 APM_DECL_SEGS
598 unsigned long flags;
599 cpumask_t cpus;
600 int cpu;
601 struct desc_struct save_desc_40;
Zachary Amsden251e6912005-10-30 14:59:34 -0800602 struct desc_struct *gdt;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700603
604 cpus = apm_save_cpus();
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100605
Linus Torvalds1da177e2005-04-16 15:20:36 -0700606 cpu = get_cpu();
Zachary Amsden251e6912005-10-30 14:59:34 -0800607 gdt = get_cpu_gdt_table(cpu);
608 save_desc_40 = gdt[0x40 / 8];
609 gdt[0x40 / 8] = bad_bios_desc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700610
Peter Zijlstra3864c482006-10-17 00:10:26 -0700611 apm_irq_save(flags);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700612 APM_DO_SAVE_SEGS;
613 apm_bios_call_asm(func, ebx_in, ecx_in, eax, ebx, ecx, edx, esi);
614 APM_DO_RESTORE_SEGS;
Peter Zijlstra3864c482006-10-17 00:10:26 -0700615 apm_irq_restore(flags);
Zachary Amsden251e6912005-10-30 14:59:34 -0800616 gdt[0x40 / 8] = save_desc_40;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700617 put_cpu();
618 apm_restore_cpus(cpus);
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100619
Linus Torvalds1da177e2005-04-16 15:20:36 -0700620 return *eax & 0xff;
621}
622
623/**
624 * apm_bios_call_simple - make a simple APM BIOS 32bit call
625 * @func: APM function to invoke
626 * @ebx_in: EBX register value for BIOS call
627 * @ecx_in: ECX register value for BIOS call
628 * @eax: EAX register on return from the BIOS call
629 *
Andreas Mohr87af2ff2006-06-23 02:04:17 -0700630 * Make a BIOS call that returns one value only, or just status.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700631 * If there is an error, then the error code is returned in AH
632 * (bits 8-15 of eax) and this function returns non-zero. This is
633 * used for simpler BIOS operations. This call may hold interrupts
634 * off for a long time on some laptops.
635 */
636
637static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax)
638{
639 u8 error;
640 APM_DECL_SEGS
641 unsigned long flags;
642 cpumask_t cpus;
643 int cpu;
644 struct desc_struct save_desc_40;
Zachary Amsden251e6912005-10-30 14:59:34 -0800645 struct desc_struct *gdt;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700646
647 cpus = apm_save_cpus();
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100648
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649 cpu = get_cpu();
Zachary Amsden251e6912005-10-30 14:59:34 -0800650 gdt = get_cpu_gdt_table(cpu);
651 save_desc_40 = gdt[0x40 / 8];
652 gdt[0x40 / 8] = bad_bios_desc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700653
Peter Zijlstra3864c482006-10-17 00:10:26 -0700654 apm_irq_save(flags);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700655 APM_DO_SAVE_SEGS;
656 error = apm_bios_call_simple_asm(func, ebx_in, ecx_in, eax);
657 APM_DO_RESTORE_SEGS;
Peter Zijlstra3864c482006-10-17 00:10:26 -0700658 apm_irq_restore(flags);
Zachary Amsden251e6912005-10-30 14:59:34 -0800659 gdt[0x40 / 8] = save_desc_40;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700660 put_cpu();
661 apm_restore_cpus(cpus);
662 return error;
663}
664
665/**
666 * apm_driver_version - APM driver version
667 * @val: loaded with the APM version on return
668 *
669 * Retrieve the APM version supported by the BIOS. This is only
670 * supported for APM 1.1 or higher. An error indicates APM 1.0 is
671 * probably present.
672 *
673 * On entry val should point to a value indicating the APM driver
674 * version with the high byte being the major and the low byte the
675 * minor number both in BCD
676 *
677 * On return it will hold the BIOS revision supported in the
678 * same format.
679 */
680
681static int apm_driver_version(u_short *val)
682{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100683 u32 eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700684
685 if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax))
686 return (eax >> 8) & 0xff;
687 *val = eax;
688 return APM_SUCCESS;
689}
690
691/**
692 * apm_get_event - get an APM event from the BIOS
693 * @event: pointer to the event
694 * @info: point to the event information
695 *
696 * The APM BIOS provides a polled information for event
697 * reporting. The BIOS expects to be polled at least every second
698 * when events are pending. When a message is found the caller should
699 * poll until no more messages are present. However, this causes
700 * problems on some laptops where a suspend event notification is
701 * not cleared until it is acknowledged.
702 *
703 * Additional information is returned in the info pointer, providing
704 * that APM 1.2 is in use. If no messges are pending the value 0x80
705 * is returned (No power management events pending).
706 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100707
Linus Torvalds1da177e2005-04-16 15:20:36 -0700708static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
709{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100710 u32 eax;
711 u32 ebx;
712 u32 ecx;
713 u32 dummy;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700714
715 if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx,
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100716 &dummy, &dummy))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700717 return (eax >> 8) & 0xff;
718 *event = ebx;
719 if (apm_info.connection_version < 0x0102)
720 *info = ~0; /* indicate info not valid */
721 else
722 *info = ecx;
723 return APM_SUCCESS;
724}
725
726/**
727 * set_power_state - set the power management state
728 * @what: which items to transition
729 * @state: state to transition to
730 *
731 * Request an APM change of state for one or more system devices. The
732 * processor state must be transitioned last of all. what holds the
733 * class of device in the upper byte and the device number (0xFF for
734 * all) for the object to be transitioned.
735 *
736 * The state holds the state to transition to, which may in fact
737 * be an acceptance of a BIOS requested state change.
738 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100739
Linus Torvalds1da177e2005-04-16 15:20:36 -0700740static int set_power_state(u_short what, u_short state)
741{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100742 u32 eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700743
744 if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax))
745 return (eax >> 8) & 0xff;
746 return APM_SUCCESS;
747}
748
749/**
750 * set_system_power_state - set system wide power state
751 * @state: which state to enter
752 *
753 * Transition the entire system into a new APM power state.
754 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100755
Linus Torvalds1da177e2005-04-16 15:20:36 -0700756static int set_system_power_state(u_short state)
757{
758 return set_power_state(APM_DEVICE_ALL, state);
759}
760
761/**
762 * apm_do_idle - perform power saving
763 *
764 * This function notifies the BIOS that the processor is (in the view
765 * of the OS) idle. It returns -1 in the event that the BIOS refuses
766 * to handle the idle request. On a success the function returns 1
767 * if the BIOS did clock slowing or 0 otherwise.
768 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100769
Linus Torvalds1da177e2005-04-16 15:20:36 -0700770static int apm_do_idle(void)
771{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100772 u32 eax;
773 u8 ret = 0;
774 int idled = 0;
775 int polling;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700776
Andi Kleen495ab9c2006-06-26 13:59:11 +0200777 polling = !!(current_thread_info()->status & TS_POLLING);
Nick Piggin64c7c8f2005-11-08 21:39:04 -0800778 if (polling) {
Andi Kleen495ab9c2006-06-26 13:59:11 +0200779 current_thread_info()->status &= ~TS_POLLING;
Ingo Molnar0888f062006-12-22 01:11:56 -0800780 /*
781 * TS_POLLING-cleared state must be visible before we
782 * test NEED_RESCHED:
783 */
784 smp_mb();
Nick Piggin64c7c8f2005-11-08 21:39:04 -0800785 }
786 if (!need_resched()) {
787 idled = 1;
788 ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax);
789 }
790 if (polling)
Andi Kleen495ab9c2006-06-26 13:59:11 +0200791 current_thread_info()->status |= TS_POLLING;
Nick Piggin64c7c8f2005-11-08 21:39:04 -0800792
793 if (!idled)
794 return 0;
795
796 if (ret) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700797 static unsigned long t;
798
799 /* This always fails on some SMP boards running UP kernels.
800 * Only report the failure the first 5 times.
801 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100802 if (++t < 5) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700803 printk(KERN_DEBUG "apm_do_idle failed (%d)\n",
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100804 (eax >> 8) & 0xff);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700805 t = jiffies;
806 }
807 return -1;
808 }
809 clock_slowed = (apm_info.bios.flags & APM_IDLE_SLOWS_CLOCK) != 0;
810 return clock_slowed;
811}
812
813/**
814 * apm_do_busy - inform the BIOS the CPU is busy
815 *
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100816 * Request that the BIOS brings the CPU back to full performance.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700817 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100818
Linus Torvalds1da177e2005-04-16 15:20:36 -0700819static void apm_do_busy(void)
820{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100821 u32 dummy;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700822
823 if (clock_slowed || ALWAYS_CALL_BUSY) {
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100824 (void)apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700825 clock_slowed = 0;
826 }
827}
828
829/*
830 * If no process has really been interested in
831 * the CPU for some time, we want to call BIOS
832 * power management - we probably want
833 * to conserve power.
834 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100835#define IDLE_CALC_LIMIT (HZ * 100)
836#define IDLE_LEAKY_MAX 16
Linus Torvalds1da177e2005-04-16 15:20:36 -0700837
Andreas Mohr87af2ff2006-06-23 02:04:17 -0700838static void (*original_pm_idle)(void) __read_mostly;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700839
Linus Torvalds1da177e2005-04-16 15:20:36 -0700840/**
841 * apm_cpu_idle - cpu idling for APM capable Linux
842 *
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100843 * This is the idling function the kernel executes when APM is available. It
Linus Torvalds1da177e2005-04-16 15:20:36 -0700844 * tries to do BIOS powermanagement based on the average system idle time.
845 * Furthermore it calls the system default idle routine.
846 */
847
848static void apm_cpu_idle(void)
849{
850 static int use_apm_idle; /* = 0 */
851 static unsigned int last_jiffies; /* = 0 */
852 static unsigned int last_stime; /* = 0 */
853
854 int apm_idle_done = 0;
855 unsigned int jiffies_since_last_check = jiffies - last_jiffies;
856 unsigned int bucket;
857
858recalc:
859 if (jiffies_since_last_check > IDLE_CALC_LIMIT) {
860 use_apm_idle = 0;
861 last_jiffies = jiffies;
862 last_stime = current->stime;
863 } else if (jiffies_since_last_check > idle_period) {
864 unsigned int idle_percentage;
865
866 idle_percentage = current->stime - last_stime;
867 idle_percentage *= 100;
868 idle_percentage /= jiffies_since_last_check;
869 use_apm_idle = (idle_percentage > idle_threshold);
870 if (apm_info.forbid_idle)
871 use_apm_idle = 0;
872 last_jiffies = jiffies;
873 last_stime = current->stime;
874 }
875
876 bucket = IDLE_LEAKY_MAX;
877
878 while (!need_resched()) {
879 if (use_apm_idle) {
880 unsigned int t;
881
882 t = jiffies;
883 switch (apm_do_idle()) {
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100884 case 0:
885 apm_idle_done = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700886 if (t != jiffies) {
887 if (bucket) {
888 bucket = IDLE_LEAKY_MAX;
889 continue;
890 }
891 } else if (bucket) {
892 bucket--;
893 continue;
894 }
895 break;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100896 case 1:
897 apm_idle_done = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700898 break;
899 default: /* BIOS refused */
900 break;
901 }
902 }
903 if (original_pm_idle)
904 original_pm_idle();
905 else
906 default_idle();
Peter Zijlstra7f424a82008-04-25 17:39:01 +0200907 local_irq_disable();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700908 jiffies_since_last_check = jiffies - last_jiffies;
909 if (jiffies_since_last_check > idle_period)
910 goto recalc;
911 }
912
913 if (apm_idle_done)
914 apm_do_busy();
Peter Zijlstra7f424a82008-04-25 17:39:01 +0200915
916 local_irq_enable();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700917}
918
919/**
920 * apm_power_off - ask the BIOS to power off
921 *
922 * Handle the power off sequence. This is the one piece of code we
923 * will execute even on SMP machines. In order to deal with BIOS
924 * bugs we support real mode APM BIOS power off calls. We also make
925 * the SMP call on CPU0 as some systems will only honour this call
926 * on their first cpu.
927 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100928
Linus Torvalds1da177e2005-04-16 15:20:36 -0700929static void apm_power_off(void)
930{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100931 unsigned char po_bios_call[] = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700932 0xb8, 0x00, 0x10, /* movw $0x1000,ax */
933 0x8e, 0xd0, /* movw ax,ss */
934 0xbc, 0x00, 0xf0, /* movw $0xf000,sp */
935 0xb8, 0x07, 0x53, /* movw $0x5307,ax */
936 0xbb, 0x01, 0x00, /* movw $0x0001,bx */
937 0xb9, 0x03, 0x00, /* movw $0x0003,cx */
938 0xcd, 0x15 /* int $0x15 */
939 };
940
Linus Torvalds1da177e2005-04-16 15:20:36 -0700941 /* Some bioses don't like being called from CPU != 0 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100942 if (apm_info.realmode_power_off) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700943 (void)apm_save_cpus();
944 machine_real_restart(po_bios_call, sizeof(po_bios_call));
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100945 } else {
946 (void)set_system_power_state(APM_STATE_OFF);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700947 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700948}
949
950#ifdef CONFIG_APM_DO_ENABLE
951
952/**
953 * apm_enable_power_management - enable BIOS APM power management
954 * @enable: enable yes/no
955 *
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100956 * Enable or disable the APM BIOS power services.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700957 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100958
Linus Torvalds1da177e2005-04-16 15:20:36 -0700959static int apm_enable_power_management(int enable)
960{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100961 u32 eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700962
963 if ((enable == 0) && (apm_info.bios.flags & APM_BIOS_DISENGAGED))
964 return APM_NOT_ENGAGED;
965 if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100966 enable, &eax))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700967 return (eax >> 8) & 0xff;
968 if (enable)
969 apm_info.bios.flags &= ~APM_BIOS_DISABLED;
970 else
971 apm_info.bios.flags |= APM_BIOS_DISABLED;
972 return APM_SUCCESS;
973}
974#endif
975
976/**
977 * apm_get_power_status - get current power state
978 * @status: returned status
979 * @bat: battery info
980 * @life: estimated life
981 *
982 * Obtain the current power status from the APM BIOS. We return a
983 * status which gives the rough battery status, and current power
984 * source. The bat value returned give an estimate as a percentage
985 * of life and a status value for the battery. The estimated life
986 * if reported is a lifetime in secodnds/minutes at current powwer
987 * consumption.
988 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100989
Linus Torvalds1da177e2005-04-16 15:20:36 -0700990static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
991{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +0100992 u32 eax;
993 u32 ebx;
994 u32 ecx;
995 u32 edx;
996 u32 dummy;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700997
998 if (apm_info.get_power_status_broken)
999 return APM_32_UNSUPPORTED;
1000 if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0,
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001001 &eax, &ebx, &ecx, &edx, &dummy))
Linus Torvalds1da177e2005-04-16 15:20:36 -07001002 return (eax >> 8) & 0xff;
1003 *status = ebx;
1004 *bat = ecx;
1005 if (apm_info.get_power_status_swabinminutes) {
1006 *life = swab16((u16)edx);
1007 *life |= 0x8000;
1008 } else
1009 *life = edx;
1010 return APM_SUCCESS;
1011}
1012
1013#if 0
1014static int apm_get_battery_status(u_short which, u_short *status,
1015 u_short *bat, u_short *life, u_short *nbat)
1016{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001017 u32 eax;
1018 u32 ebx;
1019 u32 ecx;
1020 u32 edx;
1021 u32 esi;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001022
1023 if (apm_info.connection_version < 0x0102) {
1024 /* pretend we only have one battery. */
1025 if (which != 1)
1026 return APM_BAD_DEVICE;
1027 *nbat = 1;
1028 return apm_get_power_status(status, bat, life);
1029 }
1030
1031 if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax,
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001032 &ebx, &ecx, &edx, &esi))
Linus Torvalds1da177e2005-04-16 15:20:36 -07001033 return (eax >> 8) & 0xff;
1034 *status = ebx;
1035 *bat = ecx;
1036 *life = edx;
1037 *nbat = esi;
1038 return APM_SUCCESS;
1039}
1040#endif
1041
1042/**
1043 * apm_engage_power_management - enable PM on a device
1044 * @device: identity of device
1045 * @enable: on/off
1046 *
1047 * Activate or deactive power management on either a specific device
1048 * or the entire system (%APM_DEVICE_ALL).
1049 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001050
Linus Torvalds1da177e2005-04-16 15:20:36 -07001051static int apm_engage_power_management(u_short device, int enable)
1052{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001053 u32 eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001054
1055 if ((enable == 0) && (device == APM_DEVICE_ALL)
1056 && (apm_info.bios.flags & APM_BIOS_DISABLED))
1057 return APM_DISABLED;
1058 if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable, &eax))
1059 return (eax >> 8) & 0xff;
1060 if (device == APM_DEVICE_ALL) {
1061 if (enable)
1062 apm_info.bios.flags &= ~APM_BIOS_DISENGAGED;
1063 else
1064 apm_info.bios.flags |= APM_BIOS_DISENGAGED;
1065 }
1066 return APM_SUCCESS;
1067}
1068
1069#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
1070
1071/**
1072 * apm_console_blank - blank the display
1073 * @blank: on/off
1074 *
1075 * Attempt to blank the console, firstly by blanking just video device
1076 * zero, and if that fails (some BIOSes don't support it) then it blanks
1077 * all video devices. Typically the BIOS will do laptop backlight and
1078 * monitor powerdown for us.
1079 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001080
Linus Torvalds1da177e2005-04-16 15:20:36 -07001081static int apm_console_blank(int blank)
1082{
Andreas Mohr87af2ff2006-06-23 02:04:17 -07001083 int error = APM_NOT_ENGAGED; /* silence gcc */
1084 int i;
Jordan Crouse3841b0a2006-01-06 00:12:16 -08001085 u_short state;
1086 static const u_short dev[3] = { 0x100, 0x1FF, 0x101 };
Linus Torvalds1da177e2005-04-16 15:20:36 -07001087
1088 state = blank ? APM_STATE_STANDBY : APM_STATE_READY;
Jordan Crouse3841b0a2006-01-06 00:12:16 -08001089
1090 for (i = 0; i < ARRAY_SIZE(dev); i++) {
1091 error = set_power_state(dev[i], state);
1092
1093 if ((error == APM_SUCCESS) || (error == APM_NO_ERROR))
1094 return 1;
1095
1096 if (error == APM_NOT_ENGAGED)
1097 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001098 }
Jordan Crouse3841b0a2006-01-06 00:12:16 -08001099
Samuel Thibault73374452006-04-18 22:21:50 -07001100 if (error == APM_NOT_ENGAGED) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001101 static int tried;
1102 int eng_error;
1103 if (tried++ == 0) {
1104 eng_error = apm_engage_power_management(APM_DEVICE_ALL, 1);
1105 if (eng_error) {
1106 apm_error("set display", error);
1107 apm_error("engage interface", eng_error);
1108 return 0;
1109 } else
1110 return apm_console_blank(blank);
1111 }
1112 }
1113 apm_error("set display", error);
1114 return 0;
1115}
1116#endif
1117
1118static int queue_empty(struct apm_user *as)
1119{
1120 return as->event_head == as->event_tail;
1121}
1122
1123static apm_event_t get_queued_event(struct apm_user *as)
1124{
Andreas Mohr87af2ff2006-06-23 02:04:17 -07001125 if (++as->event_tail >= APM_MAX_EVENTS)
1126 as->event_tail = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001127 return as->events[as->event_tail];
1128}
1129
1130static void queue_event(apm_event_t event, struct apm_user *sender)
1131{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001132 struct apm_user *as;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001133
1134 spin_lock(&user_list_lock);
1135 if (user_list == NULL)
1136 goto out;
1137 for (as = user_list; as != NULL; as = as->next) {
1138 if ((as == sender) || (!as->reader))
1139 continue;
Andreas Mohr87af2ff2006-06-23 02:04:17 -07001140 if (++as->event_head >= APM_MAX_EVENTS)
1141 as->event_head = 0;
1142
Linus Torvalds1da177e2005-04-16 15:20:36 -07001143 if (as->event_head == as->event_tail) {
1144 static int notified;
1145
1146 if (notified++ == 0)
1147 printk(KERN_ERR "apm: an event queue overflowed\n");
Andreas Mohr87af2ff2006-06-23 02:04:17 -07001148 if (++as->event_tail >= APM_MAX_EVENTS)
1149 as->event_tail = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001150 }
1151 as->events[as->event_head] = event;
1152 if ((!as->suser) || (!as->writer))
1153 continue;
1154 switch (event) {
1155 case APM_SYS_SUSPEND:
1156 case APM_USER_SUSPEND:
1157 as->suspends_pending++;
1158 suspends_pending++;
1159 break;
1160
1161 case APM_SYS_STANDBY:
1162 case APM_USER_STANDBY:
1163 as->standbys_pending++;
1164 standbys_pending++;
1165 break;
1166 }
1167 }
1168 wake_up_interruptible(&apm_waitqueue);
1169out:
1170 spin_unlock(&user_list_lock);
1171}
1172
Linus Torvalds1da177e2005-04-16 15:20:36 -07001173static void reinit_timer(void)
1174{
1175#ifdef INIT_TIMER_AFTER_SUSPEND
Ingo Molnar306e4402005-06-30 02:58:55 -07001176 unsigned long flags;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001177
1178 spin_lock_irqsave(&i8253_lock, flags);
Alan Coxc9919382007-05-08 00:29:58 -07001179 /* set the clock to HZ */
Alan Cox466eed22008-01-30 13:33:14 +01001180 outb_pit(0x34, PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001181 udelay(10);
Alan Cox466eed22008-01-30 13:33:14 +01001182 outb_pit(LATCH & 0xff, PIT_CH0); /* LSB */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001183 udelay(10);
Alan Cox466eed22008-01-30 13:33:14 +01001184 outb_pit(LATCH >> 8, PIT_CH0); /* MSB */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001185 udelay(10);
1186 spin_unlock_irqrestore(&i8253_lock, flags);
1187#endif
1188}
1189
1190static int suspend(int vetoable)
1191{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001192 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001193 struct apm_user *as;
1194
1195 if (pm_send_all(PM_SUSPEND, (void *)3)) {
1196 /* Vetoed */
1197 if (vetoable) {
1198 if (apm_info.connection_version > 0x100)
1199 set_system_power_state(APM_STATE_REJECT);
1200 err = -EBUSY;
1201 ignore_sys_suspend = 0;
1202 printk(KERN_WARNING "apm: suspend was vetoed.\n");
1203 goto out;
1204 }
1205 printk(KERN_CRIT "apm: suspend was vetoed, but suspending anyway.\n");
1206 }
1207
1208 device_suspend(PMSG_SUSPEND);
1209 local_irq_disable();
1210 device_power_down(PMSG_SUSPEND);
1211
Linus Torvalds1da177e2005-04-16 15:20:36 -07001212 local_irq_enable();
1213
1214 save_processor_state();
1215 err = set_system_power_state(APM_STATE_SUSPEND);
Thomas Hood92c6dc52005-06-13 22:58:04 -07001216 ignore_normal_resume = 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001217 restore_processor_state();
1218
1219 local_irq_disable();
john stultzc7f40ff2006-09-25 23:32:35 -07001220 reinit_timer();
Linus Torvalds1da177e2005-04-16 15:20:36 -07001221
1222 if (err == APM_NO_ERROR)
1223 err = APM_SUCCESS;
1224 if (err != APM_SUCCESS)
1225 apm_error("suspend", err);
1226 err = (err == APM_SUCCESS) ? 0 : -EIO;
1227 device_power_up();
1228 local_irq_enable();
1229 device_resume();
1230 pm_send_all(PM_RESUME, (void *)0);
1231 queue_event(APM_NORMAL_RESUME, NULL);
1232 out:
1233 spin_lock(&user_list_lock);
1234 for (as = user_list; as != NULL; as = as->next) {
1235 as->suspend_wait = 0;
1236 as->suspend_result = err;
1237 }
1238 spin_unlock(&user_list_lock);
1239 wake_up_interruptible(&apm_suspend_waitqueue);
1240 return err;
1241}
1242
1243static void standby(void)
1244{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001245 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001246
1247 local_irq_disable();
1248 device_power_down(PMSG_SUSPEND);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001249 local_irq_enable();
1250
1251 err = set_system_power_state(APM_STATE_STANDBY);
1252 if ((err != APM_SUCCESS) && (err != APM_NO_ERROR))
1253 apm_error("standby", err);
1254
1255 local_irq_disable();
1256 device_power_up();
1257 local_irq_enable();
1258}
1259
1260static apm_event_t get_event(void)
1261{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001262 int error;
1263 apm_event_t event = APM_NO_EVENTS; /* silence gcc */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001264 apm_eventinfo_t info;
1265
1266 static int notified;
1267
1268 /* we don't use the eventinfo */
1269 error = apm_get_event(&event, &info);
1270 if (error == APM_SUCCESS)
1271 return event;
1272
1273 if ((error != APM_NO_EVENTS) && (notified++ == 0))
1274 apm_error("get_event", error);
1275
1276 return 0;
1277}
1278
1279static void check_events(void)
1280{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001281 apm_event_t event;
1282 static unsigned long last_resume;
1283 static int ignore_bounce;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001284
1285 while ((event = get_event()) != 0) {
1286 if (debug) {
1287 if (event <= NR_APM_EVENT_NAME)
1288 printk(KERN_DEBUG "apm: received %s notify\n",
1289 apm_event_name[event - 1]);
1290 else
1291 printk(KERN_DEBUG "apm: received unknown "
1292 "event 0x%02x\n", event);
1293 }
1294 if (ignore_bounce
Julia Lawall4f2479f2008-01-30 13:32:18 +01001295 && (time_after(jiffies, last_resume + bounce_interval)))
Linus Torvalds1da177e2005-04-16 15:20:36 -07001296 ignore_bounce = 0;
1297
1298 switch (event) {
1299 case APM_SYS_STANDBY:
1300 case APM_USER_STANDBY:
1301 queue_event(event, NULL);
1302 if (standbys_pending <= 0)
1303 standby();
1304 break;
1305
1306 case APM_USER_SUSPEND:
1307#ifdef CONFIG_APM_IGNORE_USER_SUSPEND
1308 if (apm_info.connection_version > 0x100)
1309 set_system_power_state(APM_STATE_REJECT);
1310 break;
1311#endif
1312 case APM_SYS_SUSPEND:
1313 if (ignore_bounce) {
1314 if (apm_info.connection_version > 0x100)
1315 set_system_power_state(APM_STATE_REJECT);
1316 break;
1317 }
1318 /*
1319 * If we are already processing a SUSPEND,
1320 * then further SUSPEND events from the BIOS
1321 * will be ignored. We also return here to
1322 * cope with the fact that the Thinkpads keep
1323 * sending a SUSPEND event until something else
1324 * happens!
1325 */
1326 if (ignore_sys_suspend)
1327 return;
1328 ignore_sys_suspend = 1;
1329 queue_event(event, NULL);
1330 if (suspends_pending <= 0)
1331 (void) suspend(1);
1332 break;
1333
1334 case APM_NORMAL_RESUME:
1335 case APM_CRITICAL_RESUME:
1336 case APM_STANDBY_RESUME:
1337 ignore_sys_suspend = 0;
1338 last_resume = jiffies;
1339 ignore_bounce = 1;
1340 if ((event != APM_NORMAL_RESUME)
1341 || (ignore_normal_resume == 0)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001342 device_resume();
1343 pm_send_all(PM_RESUME, (void *)0);
1344 queue_event(event, NULL);
1345 }
1346 ignore_normal_resume = 0;
1347 break;
1348
1349 case APM_CAPABILITY_CHANGE:
1350 case APM_LOW_BATTERY:
1351 case APM_POWER_STATUS_CHANGE:
1352 queue_event(event, NULL);
1353 /* If needed, notify drivers here */
1354 break;
1355
1356 case APM_UPDATE_TIME:
Linus Torvalds1da177e2005-04-16 15:20:36 -07001357 break;
1358
1359 case APM_CRITICAL_SUSPEND:
1360 /*
1361 * We are not allowed to reject a critical suspend.
1362 */
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001363 (void)suspend(0);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001364 break;
1365 }
1366 }
1367}
1368
1369static void apm_event_handler(void)
1370{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001371 static int pending_count = 4;
1372 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001373
1374 if ((standbys_pending > 0) || (suspends_pending > 0)) {
1375 if ((apm_info.connection_version > 0x100) &&
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001376 (pending_count-- <= 0)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001377 pending_count = 4;
1378 if (debug)
1379 printk(KERN_DEBUG "apm: setting state busy\n");
1380 err = set_system_power_state(APM_STATE_BUSY);
1381 if (err)
1382 apm_error("busy", err);
1383 }
1384 } else
1385 pending_count = 4;
1386 check_events();
1387}
1388
1389/*
1390 * This is the APM thread main loop.
1391 */
1392
1393static void apm_mainloop(void)
1394{
1395 DECLARE_WAITQUEUE(wait, current);
1396
1397 add_wait_queue(&apm_waitqueue, &wait);
1398 set_current_state(TASK_INTERRUPTIBLE);
1399 for (;;) {
1400 schedule_timeout(APM_CHECK_TIMEOUT);
Serge E. Hallynfc095612006-09-29 02:00:04 -07001401 if (kthread_should_stop())
Linus Torvalds1da177e2005-04-16 15:20:36 -07001402 break;
1403 /*
1404 * Ok, check all events, check for idle (and mark us sleeping
1405 * so as not to count towards the load average)..
1406 */
1407 set_current_state(TASK_INTERRUPTIBLE);
1408 apm_event_handler();
1409 }
1410 remove_wait_queue(&apm_waitqueue, &wait);
1411}
1412
1413static int check_apm_user(struct apm_user *as, const char *func)
1414{
1415 if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
1416 printk(KERN_ERR "apm: %s passed bad filp\n", func);
1417 return 1;
1418 }
1419 return 0;
1420}
1421
1422static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
1423{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001424 struct apm_user *as;
1425 int i;
1426 apm_event_t event;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001427
1428 as = fp->private_data;
1429 if (check_apm_user(as, "read"))
1430 return -EIO;
1431 if ((int)count < sizeof(apm_event_t))
1432 return -EINVAL;
1433 if ((queue_empty(as)) && (fp->f_flags & O_NONBLOCK))
1434 return -EAGAIN;
1435 wait_event_interruptible(apm_waitqueue, !queue_empty(as));
1436 i = count;
1437 while ((i >= sizeof(event)) && !queue_empty(as)) {
1438 event = get_queued_event(as);
1439 if (copy_to_user(buf, &event, sizeof(event))) {
1440 if (i < count)
1441 break;
1442 return -EFAULT;
1443 }
1444 switch (event) {
1445 case APM_SYS_SUSPEND:
1446 case APM_USER_SUSPEND:
1447 as->suspends_read++;
1448 break;
1449
1450 case APM_SYS_STANDBY:
1451 case APM_USER_STANDBY:
1452 as->standbys_read++;
1453 break;
1454 }
1455 buf += sizeof(event);
1456 i -= sizeof(event);
1457 }
1458 if (i < count)
1459 return count - i;
1460 if (signal_pending(current))
1461 return -ERESTARTSYS;
1462 return 0;
1463}
1464
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001465static unsigned int do_poll(struct file *fp, poll_table *wait)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001466{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001467 struct apm_user *as;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001468
1469 as = fp->private_data;
1470 if (check_apm_user(as, "poll"))
1471 return 0;
1472 poll_wait(fp, &apm_waitqueue, wait);
1473 if (!queue_empty(as))
1474 return POLLIN | POLLRDNORM;
1475 return 0;
1476}
1477
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001478static int do_ioctl(struct inode *inode, struct file *filp,
Linus Torvalds1da177e2005-04-16 15:20:36 -07001479 u_int cmd, u_long arg)
1480{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001481 struct apm_user *as;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001482
1483 as = filp->private_data;
1484 if (check_apm_user(as, "ioctl"))
1485 return -EIO;
1486 if ((!as->suser) || (!as->writer))
1487 return -EPERM;
1488 switch (cmd) {
1489 case APM_IOC_STANDBY:
1490 if (as->standbys_read > 0) {
1491 as->standbys_read--;
1492 as->standbys_pending--;
1493 standbys_pending--;
1494 } else
1495 queue_event(APM_USER_STANDBY, as);
1496 if (standbys_pending <= 0)
1497 standby();
1498 break;
1499 case APM_IOC_SUSPEND:
1500 if (as->suspends_read > 0) {
1501 as->suspends_read--;
1502 as->suspends_pending--;
1503 suspends_pending--;
1504 } else
1505 queue_event(APM_USER_SUSPEND, as);
1506 if (suspends_pending <= 0) {
1507 return suspend(1);
1508 } else {
1509 as->suspend_wait = 1;
1510 wait_event_interruptible(apm_suspend_waitqueue,
1511 as->suspend_wait == 0);
1512 return as->suspend_result;
1513 }
1514 break;
1515 default:
1516 return -EINVAL;
1517 }
1518 return 0;
1519}
1520
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001521static int do_release(struct inode *inode, struct file *filp)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001522{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001523 struct apm_user *as;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001524
1525 as = filp->private_data;
1526 if (check_apm_user(as, "release"))
1527 return 0;
1528 filp->private_data = NULL;
1529 if (as->standbys_pending > 0) {
1530 standbys_pending -= as->standbys_pending;
1531 if (standbys_pending <= 0)
1532 standby();
1533 }
1534 if (as->suspends_pending > 0) {
1535 suspends_pending -= as->suspends_pending;
1536 if (suspends_pending <= 0)
1537 (void) suspend(1);
1538 }
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001539 spin_lock(&user_list_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001540 if (user_list == as)
1541 user_list = as->next;
1542 else {
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001543 struct apm_user *as1;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001544
1545 for (as1 = user_list;
1546 (as1 != NULL) && (as1->next != as);
1547 as1 = as1->next)
1548 ;
1549 if (as1 == NULL)
1550 printk(KERN_ERR "apm: filp not in user list\n");
1551 else
1552 as1->next = as->next;
1553 }
1554 spin_unlock(&user_list_lock);
1555 kfree(as);
1556 return 0;
1557}
1558
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001559static int do_open(struct inode *inode, struct file *filp)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001560{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001561 struct apm_user *as;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001562
Robert P. J. Day5cbded52006-12-13 00:35:56 -08001563 as = kmalloc(sizeof(*as), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001564 if (as == NULL) {
1565 printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
1566 sizeof(*as));
1567 return -ENOMEM;
1568 }
1569 as->magic = APM_BIOS_MAGIC;
1570 as->event_tail = as->event_head = 0;
1571 as->suspends_pending = as->standbys_pending = 0;
1572 as->suspends_read = as->standbys_read = 0;
1573 /*
1574 * XXX - this is a tiny bit broken, when we consider BSD
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001575 * process accounting. If the device is opened by root, we
Linus Torvalds1da177e2005-04-16 15:20:36 -07001576 * instantly flag that we used superuser privs. Who knows,
1577 * we might close the device immediately without doing a
1578 * privileged operation -- cevans
1579 */
1580 as->suser = capable(CAP_SYS_ADMIN);
1581 as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
1582 as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
1583 spin_lock(&user_list_lock);
1584 as->next = user_list;
1585 user_list = as;
1586 spin_unlock(&user_list_lock);
1587 filp->private_data = as;
1588 return 0;
1589}
1590
Alexey Dobriyan016d6f32007-02-13 13:26:23 +01001591static int proc_apm_show(struct seq_file *m, void *v)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001592{
Linus Torvalds1da177e2005-04-16 15:20:36 -07001593 unsigned short bx;
1594 unsigned short cx;
1595 unsigned short dx;
1596 int error;
1597 unsigned short ac_line_status = 0xff;
1598 unsigned short battery_status = 0xff;
1599 unsigned short battery_flag = 0xff;
1600 int percentage = -1;
1601 int time_units = -1;
1602 char *units = "?";
1603
Linus Torvalds1da177e2005-04-16 15:20:36 -07001604 if ((num_online_cpus() == 1) &&
1605 !(error = apm_get_power_status(&bx, &cx, &dx))) {
1606 ac_line_status = (bx >> 8) & 0xff;
1607 battery_status = bx & 0xff;
1608 if ((cx & 0xff) != 0xff)
1609 percentage = cx & 0xff;
1610
1611 if (apm_info.connection_version > 0x100) {
1612 battery_flag = (cx >> 8) & 0xff;
1613 if (dx != 0xffff) {
1614 units = (dx & 0x8000) ? "min" : "sec";
1615 time_units = dx & 0x7fff;
1616 }
1617 }
1618 }
1619 /* Arguments, with symbols from linux/apm_bios.h. Information is
1620 from the Get Power Status (0x0a) call unless otherwise noted.
1621
1622 0) Linux driver version (this will change if format changes)
1623 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
1624 2) APM flags from APM Installation Check (0x00):
1625 bit 0: APM_16_BIT_SUPPORT
1626 bit 1: APM_32_BIT_SUPPORT
1627 bit 2: APM_IDLE_SLOWS_CLOCK
1628 bit 3: APM_BIOS_DISABLED
1629 bit 4: APM_BIOS_DISENGAGED
1630 3) AC line status
1631 0x00: Off-line
1632 0x01: On-line
1633 0x02: On backup power (BIOS >= 1.1 only)
1634 0xff: Unknown
1635 4) Battery status
1636 0x00: High
1637 0x01: Low
1638 0x02: Critical
1639 0x03: Charging
1640 0x04: Selected battery not present (BIOS >= 1.2 only)
1641 0xff: Unknown
1642 5) Battery flag
1643 bit 0: High
1644 bit 1: Low
1645 bit 2: Critical
1646 bit 3: Charging
1647 bit 7: No system battery
1648 0xff: Unknown
1649 6) Remaining battery life (percentage of charge):
1650 0-100: valid
1651 -1: Unknown
1652 7) Remaining battery life (time units):
1653 Number of remaining minutes or seconds
1654 -1: Unknown
1655 8) min = minutes; sec = seconds */
1656
Alexey Dobriyan016d6f32007-02-13 13:26:23 +01001657 seq_printf(m, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001658 driver_version,
1659 (apm_info.bios.version >> 8) & 0xff,
1660 apm_info.bios.version & 0xff,
1661 apm_info.bios.flags,
1662 ac_line_status,
1663 battery_status,
1664 battery_flag,
1665 percentage,
1666 time_units,
1667 units);
Alexey Dobriyan016d6f32007-02-13 13:26:23 +01001668 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001669}
1670
Alexey Dobriyan016d6f32007-02-13 13:26:23 +01001671static int proc_apm_open(struct inode *inode, struct file *file)
1672{
1673 return single_open(file, proc_apm_show, NULL);
1674}
1675
1676static const struct file_operations apm_file_ops = {
1677 .owner = THIS_MODULE,
1678 .open = proc_apm_open,
1679 .read = seq_read,
1680 .llseek = seq_lseek,
1681 .release = single_release,
1682};
1683
Linus Torvalds1da177e2005-04-16 15:20:36 -07001684static int apm(void *unused)
1685{
1686 unsigned short bx;
1687 unsigned short cx;
1688 unsigned short dx;
1689 int error;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001690 char *power_stat;
1691 char *bat_stat;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001692
Linus Torvalds1da177e2005-04-16 15:20:36 -07001693#ifdef CONFIG_SMP
1694 /* 2002/08/01 - WT
1695 * This is to avoid random crashes at boot time during initialization
1696 * on SMP systems in case of "apm=power-off" mode. Seen on ASUS A7M266D.
1697 * Some bioses don't like being called from CPU != 0.
1698 * Method suggested by Ingo Molnar.
1699 */
1700 set_cpus_allowed(current, cpumask_of_cpu(0));
1701 BUG_ON(smp_processor_id() != 0);
1702#endif
1703
1704 if (apm_info.connection_version == 0) {
1705 apm_info.connection_version = apm_info.bios.version;
1706 if (apm_info.connection_version > 0x100) {
1707 /*
1708 * We only support BIOSs up to version 1.2
1709 */
1710 if (apm_info.connection_version > 0x0102)
1711 apm_info.connection_version = 0x0102;
1712 error = apm_driver_version(&apm_info.connection_version);
1713 if (error != APM_SUCCESS) {
1714 apm_error("driver version", error);
1715 /* Fall back to an APM 1.0 connection. */
1716 apm_info.connection_version = 0x100;
1717 }
1718 }
1719 }
1720
1721 if (debug)
1722 printk(KERN_INFO "apm: Connection version %d.%d\n",
1723 (apm_info.connection_version >> 8) & 0xff,
1724 apm_info.connection_version & 0xff);
1725
1726#ifdef CONFIG_APM_DO_ENABLE
1727 if (apm_info.bios.flags & APM_BIOS_DISABLED) {
1728 /*
1729 * This call causes my NEC UltraLite Versa 33/C to hang if it
1730 * is booted with PM disabled but not in the docking station.
1731 * Unfortunate ...
1732 */
1733 error = apm_enable_power_management(1);
1734 if (error) {
1735 apm_error("enable power management", error);
1736 return -1;
1737 }
1738 }
1739#endif
1740
1741 if ((apm_info.bios.flags & APM_BIOS_DISENGAGED)
1742 && (apm_info.connection_version > 0x0100)) {
1743 error = apm_engage_power_management(APM_DEVICE_ALL, 1);
1744 if (error) {
1745 apm_error("engage power management", error);
1746 return -1;
1747 }
1748 }
1749
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001750 if (debug && (num_online_cpus() == 1 || smp)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001751 error = apm_get_power_status(&bx, &cx, &dx);
1752 if (error)
1753 printk(KERN_INFO "apm: power status not available\n");
1754 else {
1755 switch ((bx >> 8) & 0xff) {
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001756 case 0:
1757 power_stat = "off line";
1758 break;
1759 case 1:
1760 power_stat = "on line";
1761 break;
1762 case 2:
1763 power_stat = "on backup power";
1764 break;
1765 default:
1766 power_stat = "unknown";
1767 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001768 }
1769 switch (bx & 0xff) {
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001770 case 0:
1771 bat_stat = "high";
1772 break;
1773 case 1:
1774 bat_stat = "low";
1775 break;
1776 case 2:
1777 bat_stat = "critical";
1778 break;
1779 case 3:
1780 bat_stat = "charging";
1781 break;
1782 default:
1783 bat_stat = "unknown";
1784 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001785 }
1786 printk(KERN_INFO
1787 "apm: AC %s, battery status %s, battery life ",
1788 power_stat, bat_stat);
1789 if ((cx & 0xff) == 0xff)
1790 printk("unknown\n");
1791 else
1792 printk("%d%%\n", cx & 0xff);
1793 if (apm_info.connection_version > 0x100) {
1794 printk(KERN_INFO
1795 "apm: battery flag 0x%02x, battery life ",
1796 (cx >> 8) & 0xff);
1797 if (dx == 0xffff)
1798 printk("unknown\n");
1799 else
1800 printk("%d %s\n", dx & 0x7fff,
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001801 (dx & 0x8000) ?
1802 "minutes" : "seconds");
Linus Torvalds1da177e2005-04-16 15:20:36 -07001803 }
1804 }
1805 }
1806
1807 /* Install our power off handler.. */
1808 if (power_off)
1809 pm_power_off = apm_power_off;
1810
1811 if (num_online_cpus() == 1 || smp) {
1812#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
1813 console_blank_hook = apm_console_blank;
1814#endif
1815 apm_mainloop();
1816#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT)
1817 console_blank_hook = NULL;
1818#endif
1819 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001820
1821 return 0;
1822}
1823
1824#ifndef MODULE
1825static int __init apm_setup(char *str)
1826{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001827 int invert;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001828
1829 while ((str != NULL) && (*str != '\0')) {
1830 if (strncmp(str, "off", 3) == 0)
1831 apm_disabled = 1;
1832 if (strncmp(str, "on", 2) == 0)
1833 apm_disabled = 0;
1834 if ((strncmp(str, "bounce-interval=", 16) == 0) ||
1835 (strncmp(str, "bounce_interval=", 16) == 0))
1836 bounce_interval = simple_strtol(str + 16, NULL, 0);
1837 if ((strncmp(str, "idle-threshold=", 15) == 0) ||
1838 (strncmp(str, "idle_threshold=", 15) == 0))
1839 idle_threshold = simple_strtol(str + 15, NULL, 0);
1840 if ((strncmp(str, "idle-period=", 12) == 0) ||
1841 (strncmp(str, "idle_period=", 12) == 0))
1842 idle_period = simple_strtol(str + 12, NULL, 0);
1843 invert = (strncmp(str, "no-", 3) == 0) ||
1844 (strncmp(str, "no_", 3) == 0);
1845 if (invert)
1846 str += 3;
1847 if (strncmp(str, "debug", 5) == 0)
1848 debug = !invert;
1849 if ((strncmp(str, "power-off", 9) == 0) ||
1850 (strncmp(str, "power_off", 9) == 0))
1851 power_off = !invert;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001852 if (strncmp(str, "smp", 3) == 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001853 smp = !invert;
1854 idle_threshold = 100;
1855 }
1856 if ((strncmp(str, "allow-ints", 10) == 0) ||
1857 (strncmp(str, "allow_ints", 10) == 0))
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001858 apm_info.allow_ints = !invert;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001859 if ((strncmp(str, "broken-psr", 10) == 0) ||
1860 (strncmp(str, "broken_psr", 10) == 0))
1861 apm_info.get_power_status_broken = !invert;
1862 if ((strncmp(str, "realmode-power-off", 18) == 0) ||
1863 (strncmp(str, "realmode_power_off", 18) == 0))
1864 apm_info.realmode_power_off = !invert;
1865 str = strchr(str, ',');
1866 if (str != NULL)
1867 str += strspn(str, ", \t");
1868 }
1869 return 1;
1870}
1871
1872__setup("apm=", apm_setup);
1873#endif
1874
Arjan van de Ven5dfe4c92007-02-12 00:55:31 -08001875static const struct file_operations apm_bios_fops = {
Linus Torvalds1da177e2005-04-16 15:20:36 -07001876 .owner = THIS_MODULE,
1877 .read = do_read,
1878 .poll = do_poll,
1879 .ioctl = do_ioctl,
1880 .open = do_open,
1881 .release = do_release,
1882};
1883
1884static struct miscdevice apm_device = {
1885 APM_MINOR_DEV,
1886 "apm_bios",
1887 &apm_bios_fops
1888};
1889
1890
1891/* Simple "print if true" callback */
Jeff Garzik18552562007-10-03 15:15:40 -04001892static int __init print_if_true(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001893{
1894 printk("%s\n", d->ident);
1895 return 0;
1896}
1897
1898/*
1899 * Some Bioses enable the PS/2 mouse (touchpad) at resume, even if it was
1900 * disabled before the suspend. Linux used to get terribly confused by that.
1901 */
Jeff Garzik18552562007-10-03 15:15:40 -04001902static int __init broken_ps2_resume(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001903{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001904 printk(KERN_INFO "%s machine detected. Mousepad Resume Bug "
1905 "workaround hopefully not needed.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001906 return 0;
1907}
1908
1909/* Some bioses have a broken protected mode poweroff and need to use realmode */
Jeff Garzik18552562007-10-03 15:15:40 -04001910static int __init set_realmode_power_off(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001911{
1912 if (apm_info.realmode_power_off == 0) {
1913 apm_info.realmode_power_off = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001914 printk(KERN_INFO "%s bios detected. "
1915 "Using realmode poweroff only.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001916 }
1917 return 0;
1918}
1919
1920/* Some laptops require interrupts to be enabled during APM calls */
Jeff Garzik18552562007-10-03 15:15:40 -04001921static int __init set_apm_ints(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001922{
1923 if (apm_info.allow_ints == 0) {
1924 apm_info.allow_ints = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001925 printk(KERN_INFO "%s machine detected. "
1926 "Enabling interrupts during APM calls.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001927 }
1928 return 0;
1929}
1930
1931/* Some APM bioses corrupt memory or just plain do not work */
Jeff Garzik18552562007-10-03 15:15:40 -04001932static int __init apm_is_horked(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001933{
1934 if (apm_info.disabled == 0) {
1935 apm_info.disabled = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001936 printk(KERN_INFO "%s machine detected. "
1937 "Disabling APM.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001938 }
1939 return 0;
1940}
1941
Jeff Garzik18552562007-10-03 15:15:40 -04001942static int __init apm_is_horked_d850md(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001943{
1944 if (apm_info.disabled == 0) {
1945 apm_info.disabled = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001946 printk(KERN_INFO "%s machine detected. "
1947 "Disabling APM.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001948 printk(KERN_INFO "This bug is fixed in bios P15 which is available for \n");
1949 printk(KERN_INFO "download from support.intel.com \n");
1950 }
1951 return 0;
1952}
1953
1954/* Some APM bioses hang on APM idle calls */
Jeff Garzik18552562007-10-03 15:15:40 -04001955static int __init apm_likes_to_melt(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001956{
1957 if (apm_info.forbid_idle == 0) {
1958 apm_info.forbid_idle = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001959 printk(KERN_INFO "%s machine detected. "
1960 "Disabling APM idle calls.\n", d->ident);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001961 }
1962 return 0;
1963}
1964
1965/*
1966 * Check for clue free BIOS implementations who use
1967 * the following QA technique
1968 *
1969 * [ Write BIOS Code ]<------
1970 * | ^
1971 * < Does it Compile >----N--
1972 * |Y ^
1973 * < Does it Boot Win98 >-N--
1974 * |Y
1975 * [Ship It]
1976 *
1977 * Phoenix A04 08/24/2000 is known bad (Dell Inspiron 5000e)
1978 * Phoenix A07 09/29/2000 is known good (Dell Inspiron 5000)
1979 */
Jeff Garzik18552562007-10-03 15:15:40 -04001980static int __init broken_apm_power(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001981{
1982 apm_info.get_power_status_broken = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001983 printk(KERN_WARNING "BIOS strings suggest APM bugs, "
1984 "disabling power status reporting.\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07001985 return 0;
1986}
1987
1988/*
1989 * This bios swaps the APM minute reporting bytes over (Many sony laptops
1990 * have this problem).
1991 */
Jeff Garzik18552562007-10-03 15:15:40 -04001992static int __init swab_apm_power_in_minutes(const struct dmi_system_id *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001993{
1994 apm_info.get_power_status_swabinminutes = 1;
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01001995 printk(KERN_WARNING "BIOS strings suggest APM reports battery life "
1996 "in minutes and wrong byte order.\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07001997 return 0;
1998}
1999
2000static struct dmi_system_id __initdata apm_dmi_table[] = {
2001 {
2002 print_if_true,
2003 KERN_WARNING "IBM T23 - BIOS 1.03b+ and controller firmware 1.02+ may be needed for Linux APM.",
2004 { DMI_MATCH(DMI_SYS_VENDOR, "IBM"),
2005 DMI_MATCH(DMI_BIOS_VERSION, "1AET38WW (1.01b)"), },
2006 },
2007 { /* Handle problems with APM on the C600 */
2008 broken_ps2_resume, "Dell Latitude C600",
2009 { DMI_MATCH(DMI_SYS_VENDOR, "Dell"),
2010 DMI_MATCH(DMI_PRODUCT_NAME, "Latitude C600"), },
2011 },
2012 { /* Allow interrupts during suspend on Dell Latitude laptops*/
2013 set_apm_ints, "Dell Latitude",
2014 { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
2015 DMI_MATCH(DMI_PRODUCT_NAME, "Latitude C510"), }
2016 },
2017 { /* APM crashes */
2018 apm_is_horked, "Dell Inspiron 2500",
2019 { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
2020 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 2500"),
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002021 DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2022 DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
Linus Torvalds1da177e2005-04-16 15:20:36 -07002023 },
2024 { /* Allow interrupts during suspend on Dell Inspiron laptops*/
2025 set_apm_ints, "Dell Inspiron", {
2026 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
2027 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 4000"), },
2028 },
2029 { /* Handle problems with APM on Inspiron 5000e */
2030 broken_apm_power, "Dell Inspiron 5000e",
2031 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2032 DMI_MATCH(DMI_BIOS_VERSION, "A04"),
2033 DMI_MATCH(DMI_BIOS_DATE, "08/24/2000"), },
2034 },
2035 { /* Handle problems with APM on Inspiron 2500 */
2036 broken_apm_power, "Dell Inspiron 2500",
2037 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2038 DMI_MATCH(DMI_BIOS_VERSION, "A12"),
2039 DMI_MATCH(DMI_BIOS_DATE, "02/04/2002"), },
2040 },
2041 { /* APM crashes */
2042 apm_is_horked, "Dell Dimension 4100",
2043 { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
2044 DMI_MATCH(DMI_PRODUCT_NAME, "XPS-Z"),
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002045 DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
2046 DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
Linus Torvalds1da177e2005-04-16 15:20:36 -07002047 },
2048 { /* Allow interrupts during suspend on Compaq Laptops*/
2049 set_apm_ints, "Compaq 12XL125",
2050 { DMI_MATCH(DMI_SYS_VENDOR, "Compaq"),
2051 DMI_MATCH(DMI_PRODUCT_NAME, "Compaq PC"),
2052 DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002053 DMI_MATCH(DMI_BIOS_VERSION, "4.06"), },
Linus Torvalds1da177e2005-04-16 15:20:36 -07002054 },
2055 { /* Allow interrupts during APM or the clock goes slow */
2056 set_apm_ints, "ASUSTeK",
2057 { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
2058 DMI_MATCH(DMI_PRODUCT_NAME, "L8400K series Notebook PC"), },
2059 },
2060 { /* APM blows on shutdown */
2061 apm_is_horked, "ABIT KX7-333[R]",
2062 { DMI_MATCH(DMI_BOARD_VENDOR, "ABIT"),
2063 DMI_MATCH(DMI_BOARD_NAME, "VT8367-8233A (KX7-333[R])"), },
2064 },
2065 { /* APM crashes */
2066 apm_is_horked, "Trigem Delhi3",
2067 { DMI_MATCH(DMI_SYS_VENDOR, "TriGem Computer, Inc"),
2068 DMI_MATCH(DMI_PRODUCT_NAME, "Delhi3"), },
2069 },
2070 { /* APM crashes */
2071 apm_is_horked, "Fujitsu-Siemens",
2072 { DMI_MATCH(DMI_BIOS_VENDOR, "hoenix/FUJITSU SIEMENS"),
2073 DMI_MATCH(DMI_BIOS_VERSION, "Version1.01"), },
2074 },
2075 { /* APM crashes */
2076 apm_is_horked_d850md, "Intel D850MD",
2077 { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
2078 DMI_MATCH(DMI_BIOS_VERSION, "MV85010A.86A.0016.P07.0201251536"), },
2079 },
2080 { /* APM crashes */
2081 apm_is_horked, "Intel D810EMO",
2082 { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
2083 DMI_MATCH(DMI_BIOS_VERSION, "MO81010A.86A.0008.P04.0004170800"), },
2084 },
2085 { /* APM crashes */
2086 apm_is_horked, "Dell XPS-Z",
2087 { DMI_MATCH(DMI_BIOS_VENDOR, "Intel Corp."),
2088 DMI_MATCH(DMI_BIOS_VERSION, "A11"),
2089 DMI_MATCH(DMI_PRODUCT_NAME, "XPS-Z"), },
2090 },
2091 { /* APM crashes */
2092 apm_is_horked, "Sharp PC-PJ/AX",
2093 { DMI_MATCH(DMI_SYS_VENDOR, "SHARP"),
2094 DMI_MATCH(DMI_PRODUCT_NAME, "PC-PJ/AX"),
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002095 DMI_MATCH(DMI_BIOS_VENDOR, "SystemSoft"),
2096 DMI_MATCH(DMI_BIOS_VERSION, "Version R2.08"), },
Linus Torvalds1da177e2005-04-16 15:20:36 -07002097 },
2098 { /* APM crashes */
2099 apm_is_horked, "Dell Inspiron 2500",
2100 { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"),
2101 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 2500"),
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002102 DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2103 DMI_MATCH(DMI_BIOS_VERSION, "A11"), },
Linus Torvalds1da177e2005-04-16 15:20:36 -07002104 },
2105 { /* APM idle hangs */
2106 apm_likes_to_melt, "Jabil AMD",
2107 { DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
2108 DMI_MATCH(DMI_BIOS_VERSION, "0AASNP06"), },
2109 },
2110 { /* APM idle hangs */
2111 apm_likes_to_melt, "AMI Bios",
2112 { DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
2113 DMI_MATCH(DMI_BIOS_VERSION, "0AASNP05"), },
2114 },
2115 { /* Handle problems with APM on Sony Vaio PCG-N505X(DE) */
2116 swab_apm_power_in_minutes, "Sony VAIO",
2117 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2118 DMI_MATCH(DMI_BIOS_VERSION, "R0206H"),
2119 DMI_MATCH(DMI_BIOS_DATE, "08/23/99"), },
2120 },
2121 { /* Handle problems with APM on Sony Vaio PCG-N505VX */
2122 swab_apm_power_in_minutes, "Sony VAIO",
2123 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2124 DMI_MATCH(DMI_BIOS_VERSION, "W2K06H0"),
2125 DMI_MATCH(DMI_BIOS_DATE, "02/03/00"), },
2126 },
2127 { /* Handle problems with APM on Sony Vaio PCG-XG29 */
2128 swab_apm_power_in_minutes, "Sony VAIO",
2129 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2130 DMI_MATCH(DMI_BIOS_VERSION, "R0117A0"),
2131 DMI_MATCH(DMI_BIOS_DATE, "04/25/00"), },
2132 },
2133 { /* Handle problems with APM on Sony Vaio PCG-Z600NE */
2134 swab_apm_power_in_minutes, "Sony VAIO",
2135 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2136 DMI_MATCH(DMI_BIOS_VERSION, "R0121Z1"),
2137 DMI_MATCH(DMI_BIOS_DATE, "05/11/00"), },
2138 },
2139 { /* Handle problems with APM on Sony Vaio PCG-Z600NE */
2140 swab_apm_power_in_minutes, "Sony VAIO",
2141 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2142 DMI_MATCH(DMI_BIOS_VERSION, "WME01Z1"),
2143 DMI_MATCH(DMI_BIOS_DATE, "08/11/00"), },
2144 },
2145 { /* Handle problems with APM on Sony Vaio PCG-Z600LEK(DE) */
2146 swab_apm_power_in_minutes, "Sony VAIO",
2147 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2148 DMI_MATCH(DMI_BIOS_VERSION, "R0206Z3"),
2149 DMI_MATCH(DMI_BIOS_DATE, "12/25/00"), },
2150 },
2151 { /* Handle problems with APM on Sony Vaio PCG-Z505LS */
2152 swab_apm_power_in_minutes, "Sony VAIO",
2153 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2154 DMI_MATCH(DMI_BIOS_VERSION, "R0203D0"),
2155 DMI_MATCH(DMI_BIOS_DATE, "05/12/00"), },
2156 },
2157 { /* Handle problems with APM on Sony Vaio PCG-Z505LS */
2158 swab_apm_power_in_minutes, "Sony VAIO",
2159 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2160 DMI_MATCH(DMI_BIOS_VERSION, "R0203Z3"),
2161 DMI_MATCH(DMI_BIOS_DATE, "08/25/00"), },
2162 },
2163 { /* Handle problems with APM on Sony Vaio PCG-Z505LS (with updated BIOS) */
2164 swab_apm_power_in_minutes, "Sony VAIO",
2165 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2166 DMI_MATCH(DMI_BIOS_VERSION, "R0209Z3"),
2167 DMI_MATCH(DMI_BIOS_DATE, "05/12/01"), },
2168 },
2169 { /* Handle problems with APM on Sony Vaio PCG-F104K */
2170 swab_apm_power_in_minutes, "Sony VAIO",
2171 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2172 DMI_MATCH(DMI_BIOS_VERSION, "R0204K2"),
2173 DMI_MATCH(DMI_BIOS_DATE, "08/28/00"), },
2174 },
2175
2176 { /* Handle problems with APM on Sony Vaio PCG-C1VN/C1VE */
2177 swab_apm_power_in_minutes, "Sony VAIO",
2178 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2179 DMI_MATCH(DMI_BIOS_VERSION, "R0208P1"),
2180 DMI_MATCH(DMI_BIOS_DATE, "11/09/00"), },
2181 },
2182 { /* Handle problems with APM on Sony Vaio PCG-C1VE */
2183 swab_apm_power_in_minutes, "Sony VAIO",
2184 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2185 DMI_MATCH(DMI_BIOS_VERSION, "R0204P1"),
2186 DMI_MATCH(DMI_BIOS_DATE, "09/12/00"), },
2187 },
2188 { /* Handle problems with APM on Sony Vaio PCG-C1VE */
2189 swab_apm_power_in_minutes, "Sony VAIO",
2190 { DMI_MATCH(DMI_BIOS_VENDOR, "Phoenix Technologies LTD"),
2191 DMI_MATCH(DMI_BIOS_VERSION, "WXPO1Z3"),
2192 DMI_MATCH(DMI_BIOS_DATE, "10/26/01"), },
2193 },
2194 { /* broken PM poweroff bios */
2195 set_realmode_power_off, "Award Software v4.60 PGMA",
2196 { DMI_MATCH(DMI_BIOS_VENDOR, "Award Software International, Inc."),
2197 DMI_MATCH(DMI_BIOS_VERSION, "4.60 PGMA"),
2198 DMI_MATCH(DMI_BIOS_DATE, "134526184"), },
2199 },
2200
2201 /* Generic per vendor APM settings */
2202
2203 { /* Allow interrupts during suspend on IBM laptops */
2204 set_apm_ints, "IBM",
2205 { DMI_MATCH(DMI_SYS_VENDOR, "IBM"), },
2206 },
2207
2208 { }
2209};
2210
2211/*
2212 * Just start the APM thread. We do NOT want to do APM BIOS
2213 * calls from anything but the APM thread, if for no other reason
2214 * than the fact that we don't trust the APM BIOS. This way,
2215 * most common APM BIOS problems that lead to protection errors
2216 * etc will have at least some level of being contained...
2217 *
2218 * In short, if something bad happens, at least we have a choice
2219 * of just killing the apm thread..
2220 */
2221static int __init apm_init(void)
2222{
Zachary Amsden92f17f02006-01-06 00:11:58 -08002223 struct desc_struct *gdt;
Serge E. Hallynfc095612006-09-29 02:00:04 -07002224 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002225
2226 dmi_check_system(apm_dmi_table);
2227
Rusty Russell6020c8f2006-12-07 02:14:08 +01002228 if (apm_info.bios.version == 0 || paravirt_enabled()) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07002229 printk(KERN_INFO "apm: BIOS not found.\n");
2230 return -ENODEV;
2231 }
2232 printk(KERN_INFO
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002233 "apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n",
2234 ((apm_info.bios.version >> 8) & 0xff),
2235 (apm_info.bios.version & 0xff),
2236 apm_info.bios.flags,
2237 driver_version);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002238 if ((apm_info.bios.flags & APM_32_BIT_SUPPORT) == 0) {
2239 printk(KERN_INFO "apm: no 32 bit BIOS support\n");
2240 return -ENODEV;
2241 }
2242
2243 if (allow_ints)
2244 apm_info.allow_ints = 1;
2245 if (broken_psr)
2246 apm_info.get_power_status_broken = 1;
2247 if (realmode_power_off)
2248 apm_info.realmode_power_off = 1;
2249 /* User can override, but default is to trust DMI */
2250 if (apm_disabled != -1)
2251 apm_info.disabled = apm_disabled;
2252
2253 /*
2254 * Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
2255 * but is reportedly a 1.0 BIOS.
2256 */
2257 if (apm_info.bios.version == 0x001)
2258 apm_info.bios.version = 0x100;
2259
2260 /* BIOS < 1.2 doesn't set cseg_16_len */
2261 if (apm_info.bios.version < 0x102)
2262 apm_info.bios.cseg_16_len = 0; /* 64k */
2263
2264 if (debug) {
Eugene Teo07a30462007-07-31 00:38:15 -07002265 printk(KERN_INFO "apm: entry %x:%x cseg16 %x dseg %x",
Linus Torvalds1da177e2005-04-16 15:20:36 -07002266 apm_info.bios.cseg, apm_info.bios.offset,
2267 apm_info.bios.cseg_16, apm_info.bios.dseg);
2268 if (apm_info.bios.version > 0x100)
2269 printk(" cseg len %x, dseg len %x",
2270 apm_info.bios.cseg_len,
2271 apm_info.bios.dseg_len);
2272 if (apm_info.bios.version > 0x101)
2273 printk(" cseg16 len %x", apm_info.bios.cseg_16_len);
2274 printk("\n");
2275 }
2276
2277 if (apm_info.disabled) {
2278 printk(KERN_NOTICE "apm: disabled on user request.\n");
2279 return -ENODEV;
2280 }
2281 if ((num_online_cpus() > 1) && !power_off && !smp) {
2282 printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
2283 apm_info.disabled = 1;
2284 return -ENODEV;
2285 }
Len Brown9f9adec2007-12-13 17:38:03 -05002286 if (pm_flags & PM_ACPI) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07002287 printk(KERN_NOTICE "apm: overridden by ACPI.\n");
2288 apm_info.disabled = 1;
2289 return -ENODEV;
2290 }
Len Brown9f9adec2007-12-13 17:38:03 -05002291 pm_flags |= PM_APM;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002292
2293 /*
2294 * Set up a segment that references the real mode segment 0x40
2295 * that extends up to the end of page zero (that we have reserved).
2296 * This is for buggy BIOS's that refer to (real mode) segment 0x40
2297 * even though they are called in protected mode.
2298 */
2299 set_base(bad_bios_desc, __va((unsigned long)0x40 << 4));
2300 _set_limit((char *)&bad_bios_desc, 4095 - (0x40 << 4));
2301
Zachary Amsden3012d2d2006-01-06 00:11:53 -08002302 /*
2303 * Set up the long jump entry point to the APM BIOS, which is called
2304 * from inline assembly.
2305 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07002306 apm_bios_entry.offset = apm_info.bios.offset;
2307 apm_bios_entry.segment = APM_CS;
2308
Zachary Amsden3012d2d2006-01-06 00:11:53 -08002309 /*
2310 * The APM 1.1 BIOS is supposed to provide limit information that it
2311 * recognizes. Many machines do this correctly, but many others do
2312 * not restrict themselves to their claimed limit. When this happens,
2313 * they will cause a segmentation violation in the kernel at boot time.
2314 * Most BIOS's, however, will respect a 64k limit, so we use that.
Zachary Amsden92f17f02006-01-06 00:11:58 -08002315 *
2316 * Note we only set APM segments on CPU zero, since we pin the APM
2317 * code to that CPU.
Zachary Amsden3012d2d2006-01-06 00:11:53 -08002318 */
Zachary Amsden92f17f02006-01-06 00:11:58 -08002319 gdt = get_cpu_gdt_table(0);
2320 set_base(gdt[APM_CS >> 3],
2321 __va((unsigned long)apm_info.bios.cseg << 4));
2322 set_base(gdt[APM_CS_16 >> 3],
2323 __va((unsigned long)apm_info.bios.cseg_16 << 4));
2324 set_base(gdt[APM_DS >> 3],
2325 __va((unsigned long)apm_info.bios.dseg << 4));
Linus Torvalds1da177e2005-04-16 15:20:36 -07002326
Alexey Dobriyan11ae9dd2008-02-26 13:23:32 +03002327 proc_create("apm", 0, NULL, &apm_file_ops);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002328
Serge E. Hallynfc095612006-09-29 02:00:04 -07002329 kapmd_task = kthread_create(apm, NULL, "kapmd");
2330 if (IS_ERR(kapmd_task)) {
2331 printk(KERN_ERR "apm: disabled - Unable to start kernel "
2332 "thread.\n");
2333 err = PTR_ERR(kapmd_task);
2334 kapmd_task = NULL;
Neil Hormanc94a62a2006-09-25 23:32:21 -07002335 remove_proc_entry("apm", NULL);
Serge E. Hallynfc095612006-09-29 02:00:04 -07002336 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002337 }
Serge E. Hallynfc095612006-09-29 02:00:04 -07002338 wake_up_process(kapmd_task);
Linus Torvalds1da177e2005-04-16 15:20:36 -07002339
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002340 if (num_online_cpus() > 1 && !smp) {
Linus Torvalds1da177e2005-04-16 15:20:36 -07002341 printk(KERN_NOTICE
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002342 "apm: disabled - APM is not SMP safe (power off active).\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07002343 return 0;
2344 }
2345
Neil Hormanc94a62a2006-09-25 23:32:21 -07002346 /*
2347 * Note we don't actually care if the misc_device cannot be registered.
2348 * this driver can do its job without it, even if userspace can't
2349 * control it. just log the error
2350 */
2351 if (misc_register(&apm_device))
2352 printk(KERN_WARNING "apm: Could not register misc device.\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -07002353
2354 if (HZ != 100)
2355 idle_period = (idle_period * HZ) / 100;
2356 if (idle_threshold < 100) {
2357 original_pm_idle = pm_idle;
2358 pm_idle = apm_cpu_idle;
2359 set_pm_idle = 1;
2360 }
2361
2362 return 0;
2363}
2364
2365static void __exit apm_exit(void)
2366{
Cyrill Gorcunov3f4380a2008-01-30 13:32:32 +01002367 int error;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002368
2369 if (set_pm_idle) {
2370 pm_idle = original_pm_idle;
2371 /*
2372 * We are about to unload the current idle thread pm callback
2373 * (pm_idle), Wait for all processors to update cached/local
2374 * copies of pm_idle before proceeding.
2375 */
2376 cpu_idle_wait();
2377 }
2378 if (((apm_info.bios.flags & APM_BIOS_DISENGAGED) == 0)
2379 && (apm_info.connection_version > 0x0100)) {
2380 error = apm_engage_power_management(APM_DEVICE_ALL, 0);
2381 if (error)
2382 apm_error("disengage power management", error);
2383 }
2384 misc_deregister(&apm_device);
2385 remove_proc_entry("apm", NULL);
2386 if (power_off)
2387 pm_power_off = NULL;
Serge E. Hallynfc095612006-09-29 02:00:04 -07002388 if (kapmd_task) {
2389 kthread_stop(kapmd_task);
2390 kapmd_task = NULL;
2391 }
Len Brown9f9adec2007-12-13 17:38:03 -05002392 pm_flags &= ~PM_APM;
Linus Torvalds1da177e2005-04-16 15:20:36 -07002393}
2394
2395module_init(apm_init);
2396module_exit(apm_exit);
2397
2398MODULE_AUTHOR("Stephen Rothwell");
2399MODULE_DESCRIPTION("Advanced Power Management");
2400MODULE_LICENSE("GPL");
2401module_param(debug, bool, 0644);
2402MODULE_PARM_DESC(debug, "Enable debug mode");
2403module_param(power_off, bool, 0444);
2404MODULE_PARM_DESC(power_off, "Enable power off");
2405module_param(bounce_interval, int, 0444);
2406MODULE_PARM_DESC(bounce_interval,
2407 "Set the number of ticks to ignore suspend bounces");
2408module_param(allow_ints, bool, 0444);
2409MODULE_PARM_DESC(allow_ints, "Allow interrupts during BIOS calls");
2410module_param(broken_psr, bool, 0444);
2411MODULE_PARM_DESC(broken_psr, "BIOS has a broken GetPowerStatus call");
2412module_param(realmode_power_off, bool, 0444);
2413MODULE_PARM_DESC(realmode_power_off,
2414 "Switch to real mode before powering off");
2415module_param(idle_threshold, int, 0444);
2416MODULE_PARM_DESC(idle_threshold,
2417 "System idle percentage above which to make APM BIOS idle calls");
2418module_param(idle_period, int, 0444);
2419MODULE_PARM_DESC(idle_period,
2420 "Period (in sec/100) over which to caculate the idle percentage");
2421module_param(smp, bool, 0444);
2422MODULE_PARM_DESC(smp,
2423 "Set this to enable APM use on an SMP platform. Use with caution on older systems");
2424MODULE_ALIAS_MISCDEV(APM_MINOR_DEV);