patch-2.1.122 linux/drivers/char/apm_bios.c
Next file: linux/drivers/char/bttv.c
Previous file: linux/drivers/char/Makefile
Back to the patch index
Back to the overall index
- Lines: 1347
- Date:
Wed Dec 31 16:00:00 1969
- Orig file:
v2.1.121/linux/drivers/char/apm_bios.c
- Orig date:
Sat Sep 5 16:46:40 1998
diff -u --recursive --new-file v2.1.121/linux/drivers/char/apm_bios.c linux/drivers/char/apm_bios.c
@@ -1,1346 +0,0 @@
-/* -*- linux-c -*-
- * APM BIOS driver for Linux
- * Copyright 1994-1998 Stephen Rothwell
- * (Stephen.Rothwell@canb.auug.org.au)
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * October 1995, Rik Faith (faith@cs.unc.edu):
- * Minor enhancements and updates (to the patch set) for 1.3.x
- * Documentation
- * January 1996, Rik Faith (faith@cs.unc.edu):
- * Make /proc/apm easy to format (bump driver version)
- * March 1996, Rik Faith (faith@cs.unc.edu):
- * Prohibit APM BIOS calls unless apm_enabled.
- * (Thanks to Ulrich Windl <Ulrich.Windl@rz.uni-regensburg.de>)
- * April 1996, Stephen Rothwell (Stephen.Rothwell@canb.auug.org.au)
- * Version 1.0 and 1.1
- * May 1996, Version 1.2
- * Feb 1998, Version 1.3
- * Feb 1998, Version 1.4
- * Aug 1998, Version 1.5
- *
- * History:
- * 0.6b: first version in official kernel, Linux 1.3.46
- * 0.7: changed /proc/apm format, Linux 1.3.58
- * 0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
- * 0.9: only call bios if bios is present, Linux 1.3.72
- * 1.0: use fixed device number, consolidate /proc/apm into this file,
- * Linux 1.3.85
- * 1.1: support user-space standby and suspend, power off after system
- * halted, Linux 1.3.98
- * 1.2: When resetting RTC after resume, take care so that the time
- * is only incorrect by 30-60mS (vs. 1S previously) (Gabor J. Toth
- * <jtoth@princeton.edu>); improve interaction between
- * screen-blanking and gpm (Stephen Rothwell); Linux 1.99.4
- * 1.2a:Simple change to stop mysterious bug reports with SMP also added
- * levels to the printk calls. APM is not defined for SMP machines.
- * The new replacment for it is, but Linux doesn't yet support this.
- * Alan Cox Linux 2.1.55
- * 1.3: Set up a valid data descriptor 0x40 for buggy BIOS's
- * 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by
- * Dean Gaudet <dgaudet@arctic.org>.
- * C. Scott Ananian <cananian@alumni.princeton.edu> Linux 2.1.87
- * 1.5: Fix segment register reloading (in case of bad segments saved
- * across BIOS call).
- * Stephen ROthwell
- *
- * APM 1.1 Reference:
- *
- * Intel Corporation, Microsoft Corporation. Advanced Power Management
- * (APM) BIOS Interface Specification, Revision 1.1, September 1993.
- * Intel Order Number 241704-001. Microsoft Part Number 781-110-X01.
- *
- * [This document is available free from Intel by calling 800.628.8686 (fax
- * 916.356.6100) or 800.548.4725; or via anonymous ftp from
- * ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc. It is also
- * available from Microsoft by calling 206.882.8080.]
- *
- * APM 1.2 Reference:
- * Intel Corporation, Microsoft Corporation. Advanced Power Management
- * (APM) BIOS Interface Specification, Revision 1.2, February 1996.
- *
- * [This document is available from Intel at:
- * http://www.intel.com/IAL/powermgm
- * or Microsoft at
- * http://www.microsoft.com/windows/thirdparty/hardware/pcfuture.htm
- * ]
- */
-
-#include <linux/config.h>
-#include <linux/module.h>
-
-#include <asm/system.h>
-#include <asm/uaccess.h>
-
-#include <linux/poll.h>
-#include <linux/types.h>
-#include <linux/stddef.h>
-#include <linux/timer.h>
-#include <linux/fcntl.h>
-#include <linux/malloc.h>
-#include <linux/linkage.h>
-#ifdef CONFIG_PROC_FS
-#include <linux/stat.h>
-#include <linux/proc_fs.h>
-#endif
-#include <linux/miscdevice.h>
-#include <linux/apm_bios.h>
-#include <linux/init.h>
-
-EXPORT_SYMBOL(apm_register_callback);
-EXPORT_SYMBOL(apm_unregister_callback);
-
-extern unsigned long get_cmos_time(void);
-
-/*
- * The apm_bios device is one of the misc char devices.
- * This is its minor number.
- */
-#define APM_MINOR_DEV 134
-
-/* Configurable options:
- *
- * CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
- * This is necessary on the NEC Versa M series, which generates these when
- * resuming from SYSTEM SUSPEND. However, enabling this on other laptops
- * will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
- * USER SUSPEND is ignored -- this may prevent the APM driver from updating
- * the system time on a RESUME.
- *
- * CONFIG_APM_DO_ENABLE: enable APM features at boot time. From page 36 of
- * the specification: "When disabled, the APM BIOS does not automatically
- * power manage devices, enter the Standby State, enter the Suspend State,
- * or take power saving steps in response to CPU Idle calls." This driver
- * will make CPU Idle calls when Linux is idle (unless this feature is
- * turned off -- see below). This should always save battery power, but
- * more complicated APM features will be dependent on your BIOS
- * implementation. You may need to turn this option off if your computer
- * hangs at boot time when using APM support, or if it beeps continuously
- * instead of suspending. Turn this off if you have a NEC UltraLite Versa
- * 33/C or a Toshiba T400CDT. This is off by default since most machines
- * do fine without this feature.
- *
- * CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the
- * idle loop. On some machines, this can activate improved power savings,
- * such as a slowed CPU clock rate, when the machine is idle. These idle
- * call is made after the idle loop has run for some length of time (e.g.,
- * 333 mS). On some machines, this will cause a hang at boot time or
- * whenever the CPU becomes idle.
- *
- * CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM. Some
- * laptops can use this to turn of the LCD backlight when the VC screen
- * blanker blanks the screen. Note that this is only used by the VC screen
- * blanker, and probably won't turn off the backlight when using X11. Some
- * problems have been reported when using this option with gpm (if you'd
- * like to debug this, please do so).
- *
- * CONFIG_APM_IGNORE_MULTIPLE_SUSPEND: The IBM TP560 bios seems to insist
- * on returning multiple suspend/standby events whenever one occurs. We
- * really only need one at a time, so just ignore any beyond the first.
- * This is probably safe on most laptops.
- *
- * If you are debugging the APM support for your laptop, note that code for
- * all of these options is contained in this file, so you can #define or
- * #undef these on the next line to avoid recompiling the whole kernel.
- *
- */
-
-/* KNOWN PROBLEM MACHINES:
- *
- * U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
- * [Confirmed by TI representative]
- * U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
- * [Confirmed by BIOS disassembly]
- * [This may work now ...]
- * P: Toshiba 1950S: battery life information only gets updated after resume
- * P: Midwest Micro Soundbook Elite DX2/66 monochrome: screen blanking
- * broken in BIOS [Reported by Garst R. Reese <reese@isn.net>]
- *
- * Legend: U = unusable with APM patches
- * P = partially usable with APM patches
- */
-
-/*
- * Define to have debug messages.
- */
-#undef APM_DEBUG
-
-/*
- * Define to always call the APM BIOS busy routine even if the clock was
- * not slowed by the idle routine.
- */
-#define ALWAYS_CALL_BUSY
-
-/*
- * Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call
- * should turn interrupts on before it does a 'hlt').
- * This reportedly needs undefining for the ThinkPad 600.
- */
-#define APM_NOINTS
-
-/*
- * Define to make the APM BIOS calls zero all data segment registers (so
- * that an incorrect BIOS implementation will cause a kernel panic if it
- * tries to write to arbitrary memory).
- */
-#define APM_ZERO_SEGS
-
-/*
- * Define to make all set_limit calls use 64k limits. The APM 1.1 BIOS is
- * supposed to provide limit information that it recognizes. Many machines
- * do this correctly, but many others do not restrict themselves to their
- * claimed limit. When this happens, they will cause a segmentation
- * violation in the kernel at boot time. Most BIOS's, however, will
- * respect a 64k limit, so we use that. If you want to be pedantic and
- * hold your BIOS to its claims, then undefine this.
- */
-#define APM_RELAX_SEGMENTS
-
-/*
- * Need to poll the APM BIOS every second
- */
-#define APM_CHECK_TIMEOUT (HZ)
-
-/*
- * Save a segment register away
- */
-#define savesegment(seg, where) __asm__ __volatile__("movw %%" #seg ", %0\n" : "=m" (where))
-
-/*
- * Forward declarations
- */
-static void suspend(void);
-static void standby(void);
-static void set_time(void);
-
-static void check_events(void);
-static void do_apm_timer(unsigned long);
-
-static int do_open(struct inode *, struct file *);
-static int do_release(struct inode *, struct file *);
-static ssize_t do_read(struct file *, char *, size_t , loff_t *);
-static unsigned int do_poll(struct file *, poll_table *);
-static int do_ioctl(struct inode *, struct file *, u_int, u_long);
-
-#ifdef CONFIG_PROC_FS
-static int apm_get_info(char *, char **, off_t, int, int);
-#endif
-
-extern int apm_register_callback(int (*)(apm_event_t));
-extern void apm_unregister_callback(int (*)(apm_event_t));
-
-/*
- * Local variables
- */
-static asmlinkage struct {
- unsigned long offset;
- unsigned short segment;
-} apm_bios_entry;
-static int apm_enabled = 0;
-#ifdef CONFIG_APM_CPU_IDLE
-static int clock_slowed = 0;
-#endif
-static int suspends_pending = 0;
-static int standbys_pending = 0;
-#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
-static int waiting_for_resume = 0;
-#endif
-
-static long clock_cmos_diff;
-static int got_clock_diff = 0;
-
-static struct wait_queue * process_list = NULL;
-static struct apm_bios_struct * user_list = NULL;
-
-static struct timer_list apm_timer;
-
-static char driver_version[] = "1.5"; /* no spaces */
-
-#ifdef APM_DEBUG
-static char * apm_event_name[] = {
- "system standby",
- "system suspend",
- "normal resume",
- "critical resume",
- "low battery",
- "power status change",
- "update time",
- "critical suspend",
- "user standby",
- "user suspend",
- "system standby resume",
- "capabilities change"
-};
-#define NR_APM_EVENT_NAME \
- (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
-#endif
-
-static struct file_operations apm_bios_fops = {
- NULL, /* lseek */
- do_read,
- NULL, /* write */
- NULL, /* readdir */
- do_poll,
- do_ioctl,
- NULL, /* mmap */
- do_open,
- NULL, /* flush */
- do_release,
- NULL, /* fsync */
- NULL /* fasync */
-};
-
-static struct miscdevice apm_device = {
- APM_MINOR_DEV,
- "apm",
- &apm_bios_fops
-};
-
-typedef struct callback_list_t {
- int (* callback)(apm_event_t);
- struct callback_list_t * next;
-} callback_list_t;
-
-static callback_list_t * callback_list = NULL;
-
-typedef struct lookup_t {
- int key;
- char * msg;
-} lookup_t;
-
-static const lookup_t error_table[] = {
-/* N/A { APM_SUCCESS, "Operation succeeded" }, */
- { APM_DISABLED, "Power management disabled" },
- { APM_CONNECTED, "Real mode interface already connected" },
- { APM_NOT_CONNECTED, "Interface not connected" },
- { APM_16_CONNECTED, "16 bit interface already connected" },
-/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
- { APM_32_CONNECTED, "32 bit interface already connected" },
- { APM_32_UNSUPPORTED, "32 bit interface not supported" },
- { APM_BAD_DEVICE, "Unrecognized device ID" },
- { APM_BAD_PARAM, "Parameter out of range" },
- { APM_NOT_ENGAGED, "Interface not engaged" },
- { APM_BAD_FUNCTION, "Function not supported" },
- { APM_RESUME_DISABLED, "Resume timer disabled" },
- { APM_BAD_STATE, "Unable to enter requested state" },
-/* N/A { APM_NO_EVENTS, "No events pending" }, */
- { APM_NOT_PRESENT, "No APM present" }
-};
-#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
-
-/*
- * These are the actual BIOS calls. Depending on APM_ZERO_SEGS
- * and APM_NOINTS, we are being really paranoid here! Not only are
- * interrupts disabled, but all the segment registers (except SS) are
- * saved and zeroed this means that if the BIOS tries to reference any
- * data without explicitly loading the segment registers, the kernel will
- * fault immediately rather than have some unforeseen circumstances for
- * the rest of the kernel. And it will be very obvious! :-) Doing this
- * depends on CS referring to the same physical memory as DS so that DS
- * can be zeroed before the call. Unfortunately, we can't do anything
- * about the stack segment/pointer. Also, we tell the compiler that
- * everything could change.
- */
-
-static inline int apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
- u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
-{
- u16 old_fs;
- u16 old_gs;
- int error;
-
-#ifdef APM_ZERO_SEGS
- savesegment(fs, old_fs);
- savesegment(gs, old_gs);
-#endif
- __asm__ __volatile__(
- "pushfl\n\t"
-#ifdef APM_NOINTS
- "cli\n\t"
-#endif
-#ifdef APM_ZERO_SEGS
- "pushl %%ds\n\t"
- "pushl %%es\n\t"
- "movw %9, %%ds\n\t"
- "movw %9, %%es\n\t"
- "movw %9, %%fs\n\t"
- "movw %9, %%gs\n\t"
-#endif
- "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
- "movl $0, %%edi\n\t"
- "jnc 1f\n\t"
- "movl $1, %%edi\n"
- "1:\tpopl %%es\n\t"
- "popl %%ds\n\t"
- "popfl\n\t"
- : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
- "=S" (*esi), "=D" (error)
- : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
-#ifdef APM_ZERO_SEGS
- , "r" (0)
-#endif
- : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory");
-#ifdef APM_ZERO_SEGS
- loadsegment(fs, old_fs);
- loadsegment(gs, old_gs);
-#endif
- return error;
-}
-
-/*
- * This version only returns one value (usually an error code)
- */
-
-static inline int apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in, u32 *eax)
-{
- u16 old_fs;
- u16 old_gs;
- int error;
-
-#ifdef APM_ZERO_SEGS
- savesegment(fs, old_fs);
- savesegment(gs, old_gs);
-#endif
- __asm__ __volatile__(
- "pushfl\n\t"
-#ifdef APM_NOINTS
- "cli\n\t"
-#endif
-#ifdef APM_ZERO_SEGS
- "pushl %%ds\n\t"
- "pushl %%es\n\t"
- "movw %5, %%ds\n\t"
- "movw %5, %%es\n\t"
- "movw %5, %%fs\n\t"
- "movw %5, %%gs\n\t"
-#endif
- "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
- "movl $0, %%edi\n\t"
- "jnc 1f\n\t"
- "movl $1, %%edi\n"
- "1:\tpopl %%es\n\t"
- "popl %%ds\n\t"
- "popfl\n\t"
- : "=a" (*eax), "=D" (error)
- : "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
-#ifdef APM_ZERO_SEGS
- , "r" (0)
-#endif
- : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory");
-#ifdef APM_ZERO_SEGS
- loadsegment(fs, old_fs);
- loadsegment(gs, old_gs);
-#endif
- return error;
-}
-
-static int apm_driver_version(u_short *val)
-{
- int error;
- u32 eax;
-
- error = apm_bios_call_simple(0x530e, 0, *val, &eax);
- if (error)
- return (eax >> 8) & 0xff;
- *val = eax;
- return APM_SUCCESS;
-}
-
-static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info)
-{
- int error;
- u32 eax;
- u32 ebx;
- u32 ecx;
- u32 dummy;
-
- error = apm_bios_call(0x530b, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy);
- if (error)
- return (eax >> 8) & 0xff;
- *event = ebx;
- if (apm_bios_info.version < 0x0102)
- *info = ~0; /* indicate info not valid */
- else
- *info = ecx;
- return APM_SUCCESS;
-}
-
-static int set_power_state(u_short what, u_short state)
-{
- int error;
- u32 eax;
-
- error = apm_bios_call_simple(0x5307, what, state, &eax);
- if (error)
- return (eax >> 8) & 0xff;
- return APM_SUCCESS;
-}
-
-int apm_set_power_state(u_short state)
-{
- return set_power_state(0x0001, state);
-}
-
-#ifdef CONFIG_APM_DISPLAY_BLANK
-/* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
-static int apm_set_display_power_state(u_short state)
-{
- return set_power_state(0x01ff, state);
-}
-#endif
-
-#ifdef CONFIG_APM_DO_ENABLE
-/* Called by apm_setup if apm_enabled will be true. */
-static int apm_enable_power_management(void)
-{
- int error;
- u32 eax;
-
- error = apm_bios_call_simple(0x5308,
- (apm_bios_info.version > 0x100) ? 0x0001 : 0xffff, 1, &eax);
- if (error)
- return (eax >> 8) & 0xff;
- return APM_SUCCESS;
-}
-#endif
-
-static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
-{
- int error;
- u32 eax;
- u32 ebx;
- u32 ecx;
- u32 edx;
- u32 dummy;
-
- error = apm_bios_call(0x530a, 1, 0, &eax, &ebx, &ecx, &edx, &dummy);
- if (error)
- return (eax >> 8) & 0xff;
- *status = ebx;
- *bat = ecx;
- *life = edx;
- return APM_SUCCESS;
-}
-
-#if 0
-/* not used anywhere */
-static int apm_get_battery_status(u_short which,
- u_short *bat, u_short *life, u_short *nbat)
-{
- u_short status;
- int error;
- u32 eax;
- u32 ebx;
- u32 ecx;
- u32 edx;
- u32 esi;
-
- if (apm_bios_info.version < 0x0102) {
- /* pretend we only have one battery. */
- if (which != 1)
- return APM_BAD_DEVICE;
- *nbat = 1;
- return apm_get_power_status(&status, bat, life);
- }
-
- error = apm_bios_call(0x530a, (0x8000 | (which)), 0, &eax, &ebx, &ecx, &edx, &esi);
- if (error)
- return (eax >> 8) & 0xff;
- *bat = ecx;
- *life = edx;
- *nbat = esi;
- return APM_SUCCESS;
-}
-#endif
-
-static int apm_engage_power_management(u_short device)
-{
- int error;
- u32 eax;
-
- error = apm_bios_call_simple(0x530f, device, 1, &eax);
- if (error)
- return (eax >> 8) & 0xff;
- return APM_SUCCESS;
-}
-
-static void apm_error(char *str, int err)
-{
- int i;
-
- for (i = 0; i < ERROR_COUNT; i++)
- if (error_table[i].key == err) break;
- if (i < ERROR_COUNT)
- printk(KERN_NOTICE "apm_bios: %s: %s\n", str, error_table[i].msg);
- else
- printk(KERN_NOTICE "apm_bios: %s: unknown error code %#2.2x\n", str, err);
-}
-
-/* Called from console driver -- must make sure apm_enabled. */
-int apm_display_blank(void)
-{
-#ifdef CONFIG_APM_DISPLAY_BLANK
- int error;
-
- if (!apm_enabled)
- return 0;
- error = apm_set_display_power_state(APM_STATE_STANDBY);
- if (error == APM_SUCCESS)
- return 1;
- apm_error("set display standby", error);
-#endif
- return 0;
-}
-
-/* Called from console driver -- must make sure apm_enabled. */
-int apm_display_unblank(void)
-{
-#ifdef CONFIG_APM_DISPLAY_BLANK
- int error;
-
- if (!apm_enabled)
- return 0;
- error = apm_set_display_power_state(APM_STATE_READY);
- if (error == APM_SUCCESS)
- return 1;
- apm_error("set display ready", error);
-#endif
- return 0;
-}
-
-int apm_register_callback(int (*callback)(apm_event_t))
-{
- callback_list_t * new;
-
- new = kmalloc(sizeof(callback_list_t), GFP_KERNEL);
- if (new == NULL)
- return -ENOMEM;
- new->callback = callback;
- new->next = callback_list;
- callback_list = new;
- return 0;
-}
-
-void apm_unregister_callback(int (*callback)(apm_event_t))
-{
- callback_list_t ** ptr;
- callback_list_t * old;
-
- ptr = &callback_list;
- for (ptr = &callback_list; *ptr != NULL; ptr = &(*ptr)->next)
- if ((*ptr)->callback == callback)
- break;
- old = *ptr;
- *ptr = old->next;
- kfree_s(old, sizeof(callback_list_t));
-}
-
-static int queue_empty(struct apm_bios_struct * as)
-{
- return as->event_head == as->event_tail;
-}
-
-static apm_event_t get_queued_event(struct apm_bios_struct * as)
-{
- as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
- return as->events[as->event_tail];
-}
-
-static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
-{
- struct apm_bios_struct * as;
-
- if (user_list == NULL)
- return 0;
- for (as = user_list; as != NULL; as = as->next) {
- if (as == sender)
- continue;
- as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
- if (as->event_head == as->event_tail) {
- static int notified;
-
- if (notified == 0) {
- printk( "apm_bios: an event queue overflowed\n" );
- notified = 1;
- }
- as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
- }
- as->events[as->event_head] = event;
- if (!as->suser)
- continue;
- switch (event) {
- case APM_SYS_SUSPEND:
- case APM_USER_SUSPEND:
- as->suspends_pending++;
- suspends_pending++;
- break;
-
- case APM_SYS_STANDBY:
- case APM_USER_STANDBY:
- as->standbys_pending++;
- standbys_pending++;
- break;
- }
- }
- wake_up_interruptible(&process_list);
- return 1;
-}
-
-static void set_time(void)
-{
- unsigned long flags;
-
- if (!got_clock_diff) /* Don't know time zone, can't set clock */
- return;
-
- save_flags(flags);
- cli();
- CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
- restore_flags(flags);
-}
-
-static void suspend(void)
-{
- unsigned long flags;
- int err;
-
- /* Estimate time zone so that set_time can
- update the clock */
- save_flags(flags);
- clock_cmos_diff = -get_cmos_time();
- cli();
- clock_cmos_diff += CURRENT_TIME;
- got_clock_diff = 1;
- restore_flags(flags);
-
- err = apm_set_power_state(APM_STATE_SUSPEND);
- if (err)
- apm_error("suspend", err);
- set_time();
-}
-
-static void standby(void)
-{
- int err;
-
- err = apm_set_power_state(APM_STATE_STANDBY);
- if (err)
- apm_error("standby", err);
-}
-
-static apm_event_t get_event(void)
-{
- int error;
- apm_event_t event;
- apm_eventinfo_t info;
-
- static int notified = 0;
-
- /* we don't use the eventinfo */
- error = apm_get_event(&event, &info);
- if (error == APM_SUCCESS)
- return event;
-
- if ((error != APM_NO_EVENTS) && (notified++ == 0))
- apm_error("get_event", error);
-
- return 0;
-}
-
-static void send_event(apm_event_t event, apm_event_t undo,
- struct apm_bios_struct *sender)
-{
- callback_list_t * call;
- callback_list_t * fix;
-
- for (call = callback_list; call != NULL; call = call->next) {
- if (call->callback(event) && undo) {
- for (fix = callback_list; fix != call; fix = fix->next)
- fix->callback(undo);
- if (apm_bios_info.version > 0x100)
- apm_set_power_state(APM_STATE_REJECT);
- return;
- }
- }
-
- queue_event(event, sender);
-}
-
-static void check_events(void)
-{
- apm_event_t event;
-
- while ((event = get_event()) != 0) {
-#ifdef APM_DEBUG
- if (event <= NR_APM_EVENT_NAME)
- printk(KERN_DEBUG "APM BIOS received %s notify\n",
- apm_event_name[event - 1]);
- else
- printk(KERN_DEBUG "APM BIOS received unknown "
- "event 0x%02x\n", event);
-#endif
- switch (event) {
- case APM_SYS_STANDBY:
- case APM_USER_STANDBY:
-#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
- if (waiting_for_resume) {
- return;
- }
- waiting_for_resume = 1;
-#endif
- send_event(event, APM_STANDBY_RESUME, NULL);
- if (standbys_pending <= 0)
- standby();
- break;
-
- case APM_USER_SUSPEND:
-#ifdef CONFIG_APM_IGNORE_USER_SUSPEND
- if (apm_bios_info.version > 0x100)
- apm_set_power_state(APM_STATE_REJECT);
- break;
-#endif
- case APM_SYS_SUSPEND:
-#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
- if (waiting_for_resume) {
- return;
- }
- waiting_for_resume = 1;
-#endif
- send_event(event, APM_NORMAL_RESUME, NULL);
- if (suspends_pending <= 0)
- suspend();
- break;
-
- case APM_NORMAL_RESUME:
- case APM_CRITICAL_RESUME:
- case APM_STANDBY_RESUME:
-#ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
- waiting_for_resume = 0;
-#endif
- set_time();
- send_event(event, 0, NULL);
- break;
-
- case APM_LOW_BATTERY:
- case APM_POWER_STATUS_CHANGE:
- case APM_CAPABILITY_CHANGE:
- send_event(event, 0, NULL);
- break;
-
- case APM_UPDATE_TIME:
- set_time();
- break;
-
- case APM_CRITICAL_SUSPEND:
- suspend();
- break;
- }
- }
-}
-
-static void do_apm_timer(unsigned long unused)
-{
- int err;
-
- static int pending_count = 0;
-
- if (((standbys_pending > 0) || (suspends_pending > 0))
- && (apm_bios_info.version > 0x100)
- && (pending_count-- <= 0)) {
- pending_count = 4;
-
- err = apm_set_power_state(APM_STATE_BUSY);
- if (err)
- apm_error("busy", err);
- }
-
- if (!(((standbys_pending > 0) || (suspends_pending > 0))
- && (apm_bios_info.version == 0x100)))
- check_events();
-
- init_timer(&apm_timer);
- apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
- add_timer(&apm_timer);
-}
-
-/* Called from sys_idle, must make sure apm_enabled. */
-int apm_do_idle(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
- int error;
- u32 dummy;
-
- if (!apm_enabled)
- return 0;
-
- error = apm_bios_call_simple(0x5305, 0, 0, &dummy);
- if (error)
- return 0;
-
- clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
- return 1;
-#else
- return 0;
-#endif
-}
-
-/* Called from sys_idle, must make sure apm_enabled. */
-void apm_do_busy(void)
-{
-#ifdef CONFIG_APM_CPU_IDLE
- u32 dummy;
-
- if (apm_enabled
-#ifndef ALWAYS_CALL_BUSY
- && clock_slowed
-#endif
- ) {
- (void) apm_bios_call_simple(0x5306, 0, 0, &dummy);
- clock_slowed = 0;
- }
-#endif
-}
-
-static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
-{
- if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
- printk(KERN_ERR "apm_bios: %s passed bad filp", func);
- return 1;
- }
- return 0;
-}
-
-static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
-{
- struct apm_bios_struct * as;
- int i;
- apm_event_t event;
- struct wait_queue wait = { current, NULL };
-
- as = fp->private_data;
- if (check_apm_bios_struct(as, "read"))
- return -EIO;
- if (count < sizeof(apm_event_t))
- return -EINVAL;
- if (queue_empty(as)) {
- if (fp->f_flags & O_NONBLOCK)
- return -EAGAIN;
- add_wait_queue(&process_list, &wait);
-repeat:
- current->state = TASK_INTERRUPTIBLE;
- if (queue_empty(as) && !signal_pending(current)) {
- schedule();
- goto repeat;
- }
- current->state = TASK_RUNNING;
- remove_wait_queue(&process_list, &wait);
- }
- i = count;
- while ((i >= sizeof(event)) && !queue_empty(as)) {
- event = get_queued_event(as);
- copy_to_user(buf, &event, sizeof(event));
- switch (event) {
- case APM_SYS_SUSPEND:
- case APM_USER_SUSPEND:
- as->suspends_read++;
- break;
-
- case APM_SYS_STANDBY:
- case APM_USER_STANDBY:
- as->standbys_read++;
- break;
- }
- buf += sizeof(event);
- i -= sizeof(event);
- }
- if (i < count)
- return count - i;
- if (signal_pending(current))
- return -ERESTARTSYS;
- return 0;
-}
-
-static unsigned int do_poll(struct file *fp, poll_table * wait)
-{
- struct apm_bios_struct * as;
-
- as = fp->private_data;
- if (check_apm_bios_struct(as, "select"))
- return 0;
- poll_wait(fp, &process_list, wait);
- if (!queue_empty(as))
- return POLLIN | POLLRDNORM;
- return 0;
-}
-
-static int do_ioctl(struct inode * inode, struct file *filp,
- u_int cmd, u_long arg)
-{
- struct apm_bios_struct * as;
-
- as = filp->private_data;
- if (check_apm_bios_struct(as, "ioctl"))
- return -EIO;
- if (!as->suser)
- return -EPERM;
- switch (cmd) {
- case APM_IOC_STANDBY:
- if (as->standbys_read > 0) {
- as->standbys_read--;
- as->standbys_pending--;
- standbys_pending--;
- }
- else
- send_event(APM_USER_STANDBY, APM_STANDBY_RESUME, as);
- if (standbys_pending <= 0)
- standby();
- break;
- case APM_IOC_SUSPEND:
- if (as->suspends_read > 0) {
- as->suspends_read--;
- as->suspends_pending--;
- suspends_pending--;
- }
- else
- send_event(APM_USER_SUSPEND, APM_NORMAL_RESUME, as);
- if (suspends_pending <= 0)
- suspend();
- break;
- default:
- return -EINVAL;
- }
- return 0;
-}
-
-static int do_release(struct inode * inode, struct file * filp)
-{
- struct apm_bios_struct * as;
-
- as = filp->private_data;
- filp->private_data = NULL;
- if (check_apm_bios_struct(as, "release"))
- return 0;
- if (as->standbys_pending > 0) {
- standbys_pending -= as->standbys_pending;
- if (standbys_pending <= 0)
- standby();
- }
- if (as->suspends_pending > 0) {
- suspends_pending -= as->suspends_pending;
- if (suspends_pending <= 0)
- suspend();
- }
- if (user_list == as)
- user_list = as->next;
- else {
- struct apm_bios_struct * as1;
-
- for (as1 = user_list;
- (as1 != NULL) && (as1->next != as);
- as1 = as1->next)
- ;
- if (as1 == NULL)
- printk(KERN_ERR "apm_bios: filp not in user list");
- else
- as1->next = as->next;
- }
- kfree_s(as, sizeof(*as));
- return 0;
-}
-
-static int do_open(struct inode * inode, struct file * filp)
-{
- struct apm_bios_struct * as;
-
- as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL);
- if (as == NULL) {
- printk(KERN_ERR "apm_bios: cannot allocate struct of size %d bytes",
- sizeof(*as));
- return -ENOMEM;
- }
- as->magic = APM_BIOS_MAGIC;
- as->event_tail = as->event_head = 0;
- as->suspends_pending = as->standbys_pending = 0;
- as->suspends_read = as->standbys_read = 0;
- /*
- * XXX - this is a tiny bit broken, when we consider BSD
- * process accounting. If the device is opened by root, we
- * instantly flag that we used superuser privs. Who knows,
- * we might close the device immediately without doing a
- * privileged operation -- cevans
- */
- as->suser = capable(CAP_SYS_ADMIN);
- as->next = user_list;
- user_list = as;
- filp->private_data = as;
- return 0;
-}
-
-#ifdef CONFIG_PROC_FS
-int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
-{
- char * p;
- unsigned short bx;
- unsigned short cx;
- unsigned short dx;
- unsigned short error;
- unsigned short ac_line_status = 0xff;
- unsigned short battery_status = 0xff;
- unsigned short battery_flag = 0xff;
- int percentage = -1;
- int time_units = -1;
- char *units = "?";
-
- if (!apm_enabled)
- return 0;
- p = buf;
-
- if (!(error = apm_get_power_status(&bx, &cx, &dx))) {
- ac_line_status = (bx >> 8) & 0xff;
- battery_status = bx & 0xff;
- if ((cx & 0xff) != 0xff)
- percentage = cx & 0xff;
-
- if (apm_bios_info.version > 0x100) {
- battery_flag = (cx >> 8) & 0xff;
- if (dx != 0xffff) {
- if ((dx & 0x8000) == 0x8000) {
- units = "min";
- time_units = dx & 0x7ffe;
- } else {
- units = "sec";
- time_units = dx & 0x7fff;
- }
- }
- }
- }
- /* Arguments, with symbols from linux/apm_bios.h. Information is
- from the Get Power Status (0x0a) call unless otherwise noted.
-
- 0) Linux driver version (this will change if format changes)
- 1) APM BIOS Version. Usually 1.0 or 1.1.
- 2) APM flags from APM Installation Check (0x00):
- bit 0: APM_16_BIT_SUPPORT
- bit 1: APM_32_BIT_SUPPORT
- bit 2: APM_IDLE_SLOWS_CLOCK
- bit 3: APM_BIOS_DISABLED
- bit 4: APM_BIOS_DISENGAGED
- 3) AC line status
- 0x00: Off-line
- 0x01: On-line
- 0x02: On backup power (APM BIOS 1.1 only)
- 0xff: Unknown
- 4) Battery status
- 0x00: High
- 0x01: Low
- 0x02: Critical
- 0x03: Charging
- 0xff: Unknown
- 5) Battery flag
- bit 0: High
- bit 1: Low
- bit 2: Critical
- bit 3: Charging
- bit 7: No system battery
- 0xff: Unknown
- 6) Remaining battery life (percentage of charge):
- 0-100: valid
- -1: Unknown
- 7) Remaining battery life (time units):
- Number of remaining minutes or seconds
- -1: Unknown
- 8) min = minutes; sec = seconds */
-
- p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
- driver_version,
- (apm_bios_info.version >> 8) & 0xff,
- apm_bios_info.version & 0xff,
- apm_bios_info.flags,
- ac_line_status,
- battery_status,
- battery_flag,
- percentage,
- time_units,
- units);
-
- return p - buf;
-}
-#endif
-
-__initfunc(void apm_bios_init(void))
-{
- unsigned short bx;
- unsigned short cx;
- unsigned short dx;
- unsigned short error;
- char * power_stat;
- char * bat_stat;
- static struct proc_dir_entry *ent;
-
-#ifdef __SMP__
- if (smp_num_cpus > 1) {
- printk(KERN_NOTICE "APM disabled: APM is not SMP safe.\n");
- return;
- }
-#endif
- if (apm_bios_info.version == 0) {
- printk(KERN_INFO "APM BIOS not found.\n");
- return;
- }
- printk(KERN_INFO "APM BIOS version %c.%c Flags 0x%02x (Driver version %s)\n",
- ((apm_bios_info.version >> 8) & 0xff) + '0',
- (apm_bios_info.version & 0xff) + '0',
- apm_bios_info.flags,
- driver_version);
- if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
- printk(KERN_INFO " No 32 bit BIOS support\n");
- return;
- }
-
- /*
- * Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
- * but is reportedly a 1.0 BIOS.
- */
- if (apm_bios_info.version == 0x001)
- apm_bios_info.version = 0x100;
-
- /* BIOS < 1.2 doesn't set cseg_16_len */
- if (apm_bios_info.version < 0x102)
- apm_bios_info.cseg_16_len = 0xFFFF; /* 64k */
-
- printk(KERN_INFO " Entry %x:%lx cseg16 %x dseg %x",
- apm_bios_info.cseg, apm_bios_info.offset,
- apm_bios_info.cseg_16, apm_bios_info.dseg);
- if (apm_bios_info.version > 0x100)
- printk(" cseg len %x, cseg16 len %x, dseg len %x",
- apm_bios_info.cseg_len, apm_bios_info.cseg_16_len,
- apm_bios_info.dseg_len);
- printk("\n");
-
- /*
- * Set up a segment that references the real mode segment 0x40
- * that extends up to the end of page zero (that we have reserved).
- * This is for buggy BIOS's that refer to (real mode) segment 0x40
- * even though they are called in protected mode.
- */
- set_base(gdt[APM_40 >> 3],
- __va((unsigned long)0x40 << 4));
- set_limit(gdt[APM_40 >> 3], 4096 - (0x40 << 4));
-
- apm_bios_entry.offset = apm_bios_info.offset;
- apm_bios_entry.segment = APM_CS;
- set_base(gdt[APM_CS >> 3],
- __va((unsigned long)apm_bios_info.cseg << 4));
- set_base(gdt[APM_CS_16 >> 3],
- __va((unsigned long)apm_bios_info.cseg_16 << 4));
- set_base(gdt[APM_DS >> 3],
- __va((unsigned long)apm_bios_info.dseg << 4));
- if (apm_bios_info.version == 0x100) {
- set_limit(gdt[APM_CS >> 3], 64 * 1024);
- set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
- set_limit(gdt[APM_DS >> 3], 64 * 1024);
- } else {
-#ifdef APM_RELAX_SEGMENTS
- /* For ASUS motherboard, Award BIOS rev 110 (and others?) */
- set_limit(gdt[APM_CS >> 3], 64 * 1024);
- /* For some unknown machine. */
- set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
- /* For the DEC Hinote Ultra CT475 (and others?) */
- set_limit(gdt[APM_DS >> 3], 64 * 1024);
-#else
- set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
- set_limit(gdt[APM_CS_16 >> 3], apm_bios_info.cseg_16_len);
- set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
-#endif
- /* The APM 1.2 docs state that the apm_driver_version
- * call can fail if we try to connect as 1.2 to a 1.1 bios.
- */
- apm_bios_info.version = 0x0102;
- error = apm_driver_version(&apm_bios_info.version);
- if (error != APM_SUCCESS) { /* Fall back to an APM 1.1 connection. */
- apm_bios_info.version = 0x0101;
- error = apm_driver_version(&apm_bios_info.version);
- }
- if (error != APM_SUCCESS) /* Fall back to an APM 1.0 connection. */
- apm_bios_info.version = 0x100;
- else {
- apm_engage_power_management(0x0001);
- printk( " Connection version %d.%d\n",
- (apm_bios_info.version >> 8) & 0xff,
- apm_bios_info.version & 0xff );
- }
- }
-
- error = apm_get_power_status(&bx, &cx, &dx);
- if (error)
- printk(KERN_INFO " Power status not available\n");
- else {
- switch ((bx >> 8) & 0xff) {
- case 0: power_stat = "off line"; break;
- case 1: power_stat = "on line"; break;
- case 2: power_stat = "on backup power"; break;
- default: power_stat = "unknown"; break;
- }
- switch (bx & 0xff) {
- case 0: bat_stat = "high"; break;
- case 1: bat_stat = "low"; break;
- case 2: bat_stat = "critical"; break;
- case 3: bat_stat = "charging"; break;
- default: bat_stat = "unknown"; break;
- }
- printk(KERN_INFO " AC %s, battery status %s, battery life ",
- power_stat, bat_stat);
- if ((cx & 0xff) == 0xff)
- printk("unknown\n");
- else
- printk("%d%%\n", cx & 0xff);
- if (apm_bios_info.version > 0x100) {
- printk(" battery flag 0x%02x, battery life ",
- (cx >> 8) & 0xff);
- if (dx == 0xffff)
- printk("unknown\n");
- else {
- if ((dx & 0x8000))
- printk("%d minutes\n", dx & 0x7ffe );
- else
- printk("%d seconds\n", dx & 0x7fff );
- }
- }
- }
-
-#ifdef CONFIG_APM_DO_ENABLE
- /*
- * This call causes my NEC UltraLite Versa 33/C to hang if it is
- * booted with PM disabled but not in the docking station.
- * Unfortunate ...
- */
- error = apm_enable_power_management();
- if (error)
- apm_error("enable power management", error);
- if (error == APM_DISABLED)
- return;
-#endif
-
- init_timer(&apm_timer);
- apm_timer.function = do_apm_timer;
- apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
- add_timer(&apm_timer);
-
-#ifdef CONFIG_PROC_FS
- ent = create_proc_entry("apm", 0, 0);
- ent->get_info = apm_get_info;
-#endif
-
- misc_register(&apm_device);
-
- apm_enabled = 1;
-}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov