root/drivers/char/apm_bios.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. apm_driver_version
  2. apm_get_event
  3. apm_set_power_state
  4. apm_set_display_power_state
  5. apm_enable_power_management
  6. apm_get_power_status
  7. apm_engage_power_management
  8. apm_error
  9. apm_display_blank
  10. apm_display_unblank
  11. apm_register_callback
  12. apm_unregister_callback
  13. queue_empty
  14. get_queued_event
  15. queue_event
  16. set_time
  17. suspend
  18. standby
  19. get_event
  20. send_event
  21. check_events
  22. do_apm_timer
  23. apm_do_idle
  24. apm_do_busy
  25. check_apm_bios_struct
  26. do_read
  27. do_select
  28. do_ioctl
  29. do_release
  30. do_open
  31. apm_get_info
  32. apm_bios_init

   1 /* -*- linux-c -*-
   2  * APM BIOS driver for Linux
   3  * Copyright 1994, 1995, 1996 Stephen Rothwell
   4  *                           (Stephen.Rothwell@canb.auug.org.au)
   5  *
   6  * This program is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License as published by the
   8  * Free Software Foundation; either version 2, or (at your option) any
   9  * later version.
  10  *
  11  * This program is distributed in the hope that it will be useful, but
  12  * WITHOUT ANY WARRANTY; without even the implied warranty of
  13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14  * General Public License for more details.
  15  *
  16  * $Id: apm_bios.c,v 0.22 1995/03/09 14:12:02 sfr Exp $
  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 (Stephen.Rothwell@canb.auug.org.au)
  27  *    Version 1.0
  28  *
  29  * History:
  30  *    0.6b: first version in official kernel, Linux 1.3.46
  31  *    0.7: changed /proc/apm format, Linux 1.3.58
  32  *    0.8: fixed gcc 2.7.[12] compilation problems, Linux 1.3.59
  33  *    0.9: only call bios if bios is present, Linux 1.3.72
  34  *    1.0: use fixed device number, consolidate /proc/apm into
  35  *         this file, Linux 1.3.85
  36  *
  37  * Reference:
  38  *
  39  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
  40  *   (APM) BIOS Interface Specification, Revision 1.1, September 1993.
  41  *   Intel Order Number 241704-001.  Microsoft Part Number 781-110-X01.
  42  *
  43  * [This document is available free from Intel by calling 800.628.8686 (fax
  44  * 916.356.6100) or 800.548.4725; or via anonymous ftp from
  45  * ftp://ftp.intel.com/pub/IAL/software_specs/apmv11.doc.  It is also
  46  * available from Microsoft by calling 206.882.8080.]
  47  *
  48  */
  49 
  50 #include <linux/config.h>
  51 #include <linux/module.h>
  52 
  53 #include <asm/system.h>
  54 #include <asm/segment.h>
  55 
  56 #include <linux/types.h>
  57 #include <linux/stddef.h>
  58 #include <linux/timer.h>
  59 #include <linux/fcntl.h>
  60 #include <linux/malloc.h>
  61 #include <linux/linkage.h>
  62 #ifdef CONFIG_PROC_FS
  63 #include <linux/stat.h>
  64 #include <linux/proc_fs.h>
  65 #endif
  66 #include <linux/miscdevice.h>
  67 #include <linux/apm_bios.h>
  68 
  69 static struct symbol_table      apm_syms = {
  70 #include <linux/symtab_begin.h>
  71         X(apm_register_callback),
  72         X(apm_unregister_callback),
  73 #include <linux/symtab_end.h>
  74 };
  75 
  76 extern unsigned long get_cmos_time(void);
  77 
  78 /*
  79  * The apm_bios device is one of the misc char devices.
  80  * This is its minor number.
  81  */
  82 #define APM_MINOR_DEV   134
  83 
  84 /* Configurable options:
  85  *  
  86  * CONFIG_APM_IGNORE_USER_SUSPEND: define to ignore USER SUSPEND requests.
  87  * This is necessary on the NEC Versa M series, which generates these when
  88  * resuming from SYSTEM SUSPEND.  However, enabling this on other laptops
  89  * will cause the laptop to generate a CRITICAL SUSPEND when an appropriate
  90  * USER SUSPEND is ignored -- this may prevent the APM driver from updating
  91  * the system time on a RESUME.
  92  *
  93  * CONFIG_APM_DO_ENABLE: enable APM features at boot time.  From page 36 of
  94  * the specification: "When disabled, the APM BIOS does not automatically
  95  * power manage devices, enter the Standby State, enter the Suspend State,
  96  * or take power saving steps in response to CPU Idle calls."  This driver
  97  * will make CPU Idle calls when Linux is idle (unless this feature is
  98  * turned off -- see below).  This should always save battery power, but
  99  * more complicated APM features will be dependent on your BIOS
 100  * implementation.  You may need to turn this option off if your computer
 101  * hangs at boot time when using APM support, or if it beeps continuously
 102  * instead of suspending.  Turn this off if you have a NEC UltraLite Versa
 103  * 33/C or a Toshiba T400CDT.  This is off by default since most machines
 104  * do fine without this feature.
 105  *
 106  * CONFIG_APM_CPU_IDLE: enable calls to APM CPU Idle/CPU Busy inside the
 107  * idle loop.  On some machines, this can activate improved power savings,
 108  * such as a slowed CPU clock rate, when the machine is idle.  These idle
 109  * call is made after the idle loop has run for some length of time (e.g.,
 110  * 333 mS).  On some machines, this will cause a hang at boot time or
 111  * whenever the CPU becomes idle.
 112  *
 113  * CONFIG_APM_DISPLAY_BLANK: enable console blanking using the APM.  Some
 114  * laptops can use this to turn of the LCD backlight when the VC screen
 115  * blanker blanks the screen.  Note that this is only used by the VC screen
 116  * blanker, and probably won't turn off the backlight when using X11.
 117  *
 118  * If you are debugging the APM support for your laptop, note that code for
 119  * all of these options is contained in this file, so you can #define or
 120  * #undef these on the next line to avoid recompiling the whole kernel.
 121  *
 122  */
 123 
 124 /* KNOWN PROBLEM MACHINES:
 125  *
 126  * U: TI 4000M TravelMate: BIOS is *NOT* APM compliant
 127  *                         [Confirmed by TI representative]
 128  * U: ACER 486DX4/75: uses dseg 0040, in violation of APM specification
 129  *                    [Confirmed by BIOS disassembly]
 130  * P: Toshiba 1950S: battery life information only gets updated after resume
 131  *
 132  * Legend: U = unusable with APM patches
 133  *         P = partially usable with APM patches
 134  */
 135 
 136 /*
 137  * Define to have debug messages.
 138  */
 139 #undef APM_DEBUG
 140 
 141 /*
 142  * Define to always call the APM BIOS busy routine even if the clock was
 143  * not slowed by the idle routine.
 144  */
 145 #define ALWAYS_CALL_BUSY
 146 
 147 /*
 148  * Define to disable interrupts in APM BIOS calls (the CPU Idle BIOS call
 149  * should turn interrupts on before it does a 'hlt').
 150  */
 151 #define APM_NOINTS
 152 
 153 /*
 154  * Define to make the APM BIOS calls zero all data segment registers (do
 155  * that if an incorrect BIOS implementation will cause a kernel panic if it
 156  * tries to write to arbitrary memory).
 157  */
 158 #define APM_ZERO_SEGS
 159 
 160 /*
 161  * Define to make all set_limit calls use 64k limits.  The APM 1.1 BIOS is
 162  * supposed to provide limit information that it recognizes.  Many machines
 163  * do this correctly, but many others do not restrict themselves to their
 164  * claimed limit.  When this happens, they will cause a segmentation
 165  * violation in the kernel at boot time.  Most BIOS's, however, will
 166  * respect a 64k limit, so we use that.  If you want to be pedantic and
 167  * hold your BIOS to its claims, then undefine this.
 168  */
 169 #define APM_RELAX_SEGMENTS
 170 
 171 /*
 172  * Need to poll the APM BIOS every second
 173  */
 174 #define APM_CHECK_TIMEOUT       (HZ)
 175 
 176 /*
 177  * These are the actual BIOS calls in assembler.  Depending on
 178  * APM_ZERO_SEGS and APM_NOINTS, we are being really paranoid here!  Not
 179  * only are interrupts disabled, but all the segment registers (except SS)
 180  * are saved and zeroed this means that if the BIOS tries to reference any
 181  * data without explicitly loading the segment registers, the kernel will
 182  * fault immediately rather than have some unforeseen circumstances for the
 183  * rest of the kernel.  And it will be very obvious!  :-) Doing this
 184  * depends on CS referring to the same physical memory as DS so that DS can
 185  * be zeroed before the call. Unfortunately, we can't do anything about the
 186  * stack segment/pointer.  Also, we tell the compiler that everything could
 187  * change.
 188  */
 189 #ifdef APM_NOINTS
 190 #       define APM_DO_CLI       "cli\n\t"
 191 #else
 192 #       define APM_DO_CLI
 193 #endif
 194 #ifdef APM_ZERO_SEGS
 195 #       define APM_DO_ZERO_SEGS \
 196                 "pushl %%ds\n\t" \
 197                 "pushl %%es\n\t" \
 198                 "pushl %%fs\n\t" \
 199                 "pushl %%gs\n\t" \
 200                 "xorl %%edx, %%edx\n\t" \
 201                 "mov %%dx, %%ds\n\t" \
 202                 "mov %%dx, %%es\n\t" \
 203                 "mov %%dx, %%fs\n\t" \
 204                 "mov %%dx, %%gs\n\t"
 205 #       define APM_DO_RESTORE_SEGS      \
 206                 "popl %%gs\n\t" \
 207                 "popl %%fs\n\t" \
 208                 "popl %%es\n\t" \
 209                 "popl %%ds\n\t"
 210 #else
 211 #       define APM_DO_ZERO_SEGS
 212 #       define APM_DO_RESTORE_SEGS
 213 #endif
 214 
 215 #define APM_BIOS_CALL(error_reg) \
 216         __asm__ __volatile__( \
 217                 APM_DO_ZERO_SEGS \
 218                 "pushfl\n\t" \
 219                 APM_DO_CLI \
 220                 "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" \
 221                 "setc %%" # error_reg "\n\t" \
 222                 "popfl\n\t" \
 223                 APM_DO_RESTORE_SEGS
 224 #define APM_BIOS_CALL_END \
 225                 : "ax", "bx", "cx", "dx", "si", "di", "bp", "memory")
 226 
 227 #ifdef CONFIG_APM_CPU_IDLE
 228 #define APM_SET_CPU_IDLE(error) \
 229         APM_BIOS_CALL(al) \
 230         : "=a" (error) \
 231         : "a" (0x5305) \
 232         APM_BIOS_CALL_END
 233 #endif
 234 
 235 #define APM_SET_CPU_BUSY(error) \
 236         APM_BIOS_CALL(al) \
 237         : "=a" (error) \
 238         : "a" (0x5306) \
 239         APM_BIOS_CALL_END
 240 
 241 #define APM_SET_POWER_STATE(state, error) \
 242         APM_BIOS_CALL(al) \
 243         : "=a" (error) \
 244         : "a" (0x5307), "b" (0x0001), "c" (state) \
 245         APM_BIOS_CALL_END
 246 
 247 #ifdef CONFIG_APM_DISPLAY_BLANK
 248 #define APM_SET_DISPLAY_POWER_STATE(state, error) \
 249         APM_BIOS_CALL(al) \
 250         : "=a" (error) \
 251         : "a" (0x5307), "b" (0x01ff), "c" (state) \
 252         APM_BIOS_CALL_END
 253 #endif
 254 
 255 #ifdef CONFIG_APM_DO_ENABLE
 256 #define APM_ENABLE_POWER_MANAGEMENT(device, error) \
 257         APM_BIOS_CALL(al) \
 258         : "=a" (error) \
 259         : "a" (0x5308), "b" (device), "c" (1) \
 260         APM_BIOS_CALL_END
 261 #endif
 262 
 263 #define APM_GET_POWER_STATUS(bx, cx, dx, error) \
 264         APM_BIOS_CALL(al) \
 265         : "=a" (error), "=b" (bx), "=c" (cx), "=d" (dx) \
 266         : "a" (0x530a), "b" (1) \
 267         APM_BIOS_CALL_END
 268 
 269 #define APM_GET_EVENT(event, error)     \
 270         APM_BIOS_CALL(al) \
 271         : "=a" (error), "=b" (event) \
 272         : "a" (0x530b) \
 273         APM_BIOS_CALL_END
 274 
 275 #define APM_DRIVER_VERSION(ver, ax, error) \
 276         APM_BIOS_CALL(bl) \
 277         : "=a" (ax), "=b" (error) \
 278         : "a" (0x530e), "b" (0), "c" (ver) \
 279         APM_BIOS_CALL_END
 280 
 281 #define APM_ENGAGE_POWER_MANAGEMENT(device, error) \
 282         APM_BIOS_CALL(al) \
 283         : "=a" (error) \
 284         : "a" (0x530f), "b" (device), "c" (1) \
 285         APM_BIOS_CALL_END
 286 
 287 /*
 288  * Forward declarations
 289  */
 290 static void     suspend(void);
 291 static void     standby(void);
 292 static void     set_time(void);
 293 
 294 static void     check_events(void);
 295 static void     do_apm_timer(unsigned long);
 296 
 297 static int      do_open(struct inode *, struct file *);
 298 static void     do_release(struct inode *, struct file *);
 299 static int      do_read(struct inode *, struct file *, char *, int);
 300 static int      do_select(struct inode *, struct file *, int,
 301                           select_table *);
 302 static int      do_ioctl(struct inode *, struct file *, u_int, u_long);
 303 
 304 #ifdef CONFIG_PROC_FS
 305 static int      apm_get_info(char *, char **, off_t, int, int);
 306 #endif
 307 
 308 extern int      apm_register_callback(int (*)(apm_event_t));
 309 extern void     apm_unregister_callback(int (*)(apm_event_t));
 310 
 311 /*
 312  * Local variables
 313  */
 314 static asmlinkage struct {
 315         unsigned long   offset;
 316         unsigned short  segment;
 317 }                               apm_bios_entry;
 318 static int                      apm_enabled = 0;
 319 #ifdef CONFIG_APM_CPU_IDLE
 320 static int                      clock_slowed = 0;
 321 #endif
 322 static int                      apm_major;
 323 static int                      suspends_pending = 0;
 324 static int                      standbys_pending = 0;
 325 
 326 static long                     clock_cmos_diff;
 327 static int                      got_clock_diff = 0;
 328 
 329 static struct wait_queue *      process_list = NULL;
 330 static struct apm_bios_struct * user_list = NULL;
 331 
 332 static struct timer_list        apm_timer;
 333 
 334 static char                     driver_version[] = "1.0";/* no spaces */
 335 
 336 #ifdef APM_DEBUG
 337 static char *   apm_event_name[] = {
 338         "system standby",
 339         "system suspend",
 340         "normal resume",
 341         "critical resume",
 342         "low battery",
 343         "power status change",
 344         "update time",
 345         "critical suspend",
 346         "user standby",
 347         "user suspend",
 348         "system standby resume"
 349 };
 350 #define NR_APM_EVENT_NAME       \
 351                 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
 352 #endif
 353 
 354 static struct file_operations apm_bios_fops = {
 355         NULL,           /* lseek */
 356         do_read,
 357         NULL,           /* write */
 358         NULL,           /* readdir */
 359         do_select,
 360         do_ioctl,
 361         NULL,           /* mmap */
 362         do_open,
 363         do_release,
 364         NULL,           /* fsync */
 365         NULL            /* fasync */
 366 };
 367 
 368 static struct miscdevice apm_device = {
 369         APM_MINOR_DEV,
 370         "apm",
 371         &apm_bios_fops
 372 };
 373 
 374 #ifdef CONFIG_PROC_FS
 375 static struct proc_dir_entry    apm_proc_entry = {
 376         0, 3, "apm", S_IFREG | S_IRUGO, 1, 0, 0, 0, 0, apm_get_info
 377 };
 378 #endif
 379 
 380 typedef struct callback_list_t {
 381         int (*                          callback)(apm_event_t);
 382         struct callback_list_t *        next;
 383 } callback_list_t;
 384 
 385 static callback_list_t *        callback_list = NULL;
 386 
 387 typedef struct lookup_t {
 388         int     key;
 389         char *  msg;
 390 } lookup_t;
 391 
 392 static const lookup_t error_table[] = {
 393 /* N/A  { APM_SUCCESS,          "Operation succeeded" }, */
 394         { APM_DISABLED,         "Power management disabled" },
 395         { APM_CONNECTED,        "Real mode interface already connected" },
 396         { APM_NOT_CONNECTED,    "Interface not connected" },
 397         { APM_16_CONNECTED,     "16 bit interface already connected" },
 398 /* N/A  { APM_16_UNSUPPORTED,   "16 bit interface not supported" }, */
 399         { APM_32_CONNECTED,     "32 bit interface already connected" },
 400         { APM_32_UNSUPPORTED,   "32 bit interface not supported" },
 401         { APM_BAD_DEVICE,       "Unrecognized device ID" },
 402         { APM_BAD_PARAM,        "Parameter out of range" },
 403         { APM_NOT_ENGAGED,      "Interface not engaged" },
 404         { APM_BAD_STATE,        "Unable to enter requested state" },
 405 /* N/A  { APM_NO_EVENTS,        "No events pending" }, */
 406         { APM_NOT_PRESENT,      "No APM present" }
 407 };
 408 #define ERROR_COUNT     (sizeof(error_table)/sizeof(lookup_t))
 409 
 410 static int apm_driver_version(u_short *val)
     /* [previous][next][first][last][top][bottom][index][help] */
 411 {
 412         u_short error;
 413 
 414         APM_DRIVER_VERSION(*val, *val, error);
 415 
 416         if (error & 0xff)
 417                 return (*val >> 8);
 418         return APM_SUCCESS;
 419 }
 420 
 421 static int apm_get_event(apm_event_t *event)
     /* [previous][next][first][last][top][bottom][index][help] */
 422 {
 423         u_short error;
 424 
 425         APM_GET_EVENT(*event, error);
 426         if (error & 0xff)
 427                 return (error >> 8);
 428         return APM_SUCCESS;
 429 }
 430 
 431 static int apm_set_power_state(u_short state)
     /* [previous][next][first][last][top][bottom][index][help] */
 432 {
 433         u_short error;
 434 
 435         APM_SET_POWER_STATE(state, error);
 436         if (error & 0xff)
 437                 return (error >> 8);
 438         return APM_SUCCESS;
 439 }
 440 
 441 #ifdef CONFIG_APM_DISPLAY_BLANK
 442 /* Called by apm_display_blank and apm_display_unblank when apm_enabled. */
 443 static int apm_set_display_power_state(u_short state)
     /* [previous][next][first][last][top][bottom][index][help] */
 444 {
 445         u_short error;
 446 
 447         APM_SET_DISPLAY_POWER_STATE(state, error);
 448         if (error & 0xff)
 449                 return (error >> 8);
 450         return APM_SUCCESS;
 451 }
 452 #endif
 453 
 454 #ifdef CONFIG_APM_DO_ENABLE
 455 /* Called by apm_setup if apm_enabled will be true. */
 456 static int apm_enable_power_management(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 457 {
 458         u_short error;
 459 
 460         APM_ENABLE_POWER_MANAGEMENT((apm_bios_info.version > 0x100)
 461                                     ? 0x0001 : 0xffff,
 462                                     error);
 463         if (error & 0xff)
 464                 return (error >> 8);
 465         return APM_SUCCESS;
 466 }
 467 #endif
 468 
 469 static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
     /* [previous][next][first][last][top][bottom][index][help] */
 470 {
 471         u_short error;
 472 
 473         APM_GET_POWER_STATUS(*status, *bat, *life, error);
 474         if (error & 0xff)
 475                 return (error >> 8);
 476         return APM_SUCCESS;
 477 }
 478 
 479 static int apm_engage_power_management(u_short device)
     /* [previous][next][first][last][top][bottom][index][help] */
 480 {
 481         u_short error;
 482 
 483         APM_ENGAGE_POWER_MANAGEMENT(device, error);
 484         if (error & 0xff)
 485                 return (error >> 8);
 486         return APM_SUCCESS;
 487 }
 488 
 489 static void apm_error(char *str, int err)
     /* [previous][next][first][last][top][bottom][index][help] */
 490 {
 491         int     i;
 492 
 493         for (i = 0; i < ERROR_COUNT; i++)
 494                 if (error_table[i].key == err) break;
 495         if (i < ERROR_COUNT)
 496                 printk("apm_bios: %s: %s\n", str, error_table[i].msg);
 497         else
 498                 printk("apm_bios: %s: unknown error code %#2.2x\n", str, err);
 499 }
 500 
 501 /* Called from console driver -- must make sure apm_enabled. */
 502 int apm_display_blank(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 503 {
 504 #ifdef CONFIG_APM_DISPLAY_BLANK
 505         int     error;
 506 
 507         if (!apm_enabled)
 508                 return 0;
 509         error = apm_set_display_power_state(APM_STATE_STANDBY);
 510         if (error == APM_SUCCESS)
 511                 return 1;
 512         apm_error("set display standby", error);
 513 #endif
 514         return 0;
 515 }
 516 
 517 /* Called from console driver -- must make sure apm_enabled. */
 518 int apm_display_unblank(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 519 {
 520 #ifdef CONFIG_APM_DISPLAY_BLANK
 521         int error;
 522 
 523         if (!apm_enabled)
 524                 return 0;
 525         error = apm_set_display_power_state(APM_STATE_READY);
 526         if (error == APM_SUCCESS)
 527                 return 1;
 528         apm_error("set display ready", error);
 529 #endif
 530         return 0;
 531 }
 532 
 533 int apm_register_callback(int (*callback)(apm_event_t))
     /* [previous][next][first][last][top][bottom][index][help] */
 534 {
 535         callback_list_t *       new;
 536 
 537         new = kmalloc(sizeof(callback_list_t), GFP_KERNEL);
 538         if (new == NULL)
 539                 return -ENOMEM;
 540         new->callback = callback;
 541         new->next = callback_list;
 542         callback_list = new;
 543         return 0;
 544 }
 545 
 546 void apm_unregister_callback(int (*callback)(apm_event_t))
     /* [previous][next][first][last][top][bottom][index][help] */
 547 {
 548         callback_list_t **      ptr;
 549         callback_list_t *       old;
 550 
 551         ptr = &callback_list;
 552         for (ptr = &callback_list; *ptr != NULL; ptr = &(*ptr)->next)
 553                 if ((*ptr)->callback == callback)
 554                         break;
 555         old = *ptr;
 556         *ptr = old->next;
 557         kfree_s(old, sizeof(callback_list_t));
 558 }
 559         
 560 static int queue_empty(struct apm_bios_struct * as)
     /* [previous][next][first][last][top][bottom][index][help] */
 561 {
 562         return as->event_head == as->event_tail;
 563 }
 564 
 565 static apm_event_t get_queued_event(struct apm_bios_struct * as)
     /* [previous][next][first][last][top][bottom][index][help] */
 566 {
 567         as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
 568         return as->events[as->event_tail];
 569 }
 570 
 571 static int queue_event(apm_event_t event)
     /* [previous][next][first][last][top][bottom][index][help] */
 572 {
 573         struct apm_bios_struct *        as;
 574         
 575         if (user_list == NULL)
 576                 return 0;
 577         for (as = user_list; as != NULL; as = as->next) {
 578                 as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
 579                 if (as->event_head == as->event_tail)
 580                         as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
 581                 as->events[as->event_head] = event;
 582                 if (!as->suser)
 583                         continue;
 584                 switch (event) {
 585                 case APM_SYS_SUSPEND:
 586                 case APM_USER_SUSPEND:
 587                         as->suspends_pending++;
 588                         suspends_pending++;
 589                         break;
 590 
 591                 case APM_SYS_STANDBY:
 592                 case APM_USER_STANDBY:
 593                         as->standbys_pending++;
 594                         standbys_pending++;
 595                         break;
 596                 }
 597         }
 598         wake_up_interruptible(&process_list);
 599         return 1;
 600 }
 601 
 602 static void set_time(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 603 {
 604         unsigned long   flags;
 605 
 606         if (!got_clock_diff)    /* Don't know time zone, can't set clock */
 607                 return;
 608 
 609         save_flags(flags);
 610         cli();
 611         CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
 612         restore_flags(flags);
 613 }
 614 
 615 static void suspend(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 616 {
 617         unsigned long   flags;
 618         int             err;
 619 
 620                                 /* Estimate time zone so that set_time can
 621                                    update the clock */
 622         save_flags(flags);
 623         cli();
 624         clock_cmos_diff = CURRENT_TIME - get_cmos_time();
 625         got_clock_diff = 1;
 626         restore_flags(flags);
 627         
 628         err = apm_set_power_state(APM_STATE_SUSPEND);
 629         if (err)
 630                 apm_error("suspend", err);
 631         set_time();
 632 }
 633 
 634 static void standby(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 635 {
 636         int     err;
 637 
 638         err = apm_set_power_state(APM_STATE_STANDBY);
 639         if (err)
 640                 apm_error("standby", err);
 641 }
 642 
 643 static apm_event_t get_event(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 644 {
 645         int             error;
 646         apm_event_t     event;
 647 
 648         static int notified = 0;
 649 
 650         error = apm_get_event(&event);
 651         if (error == APM_SUCCESS)
 652                 return event;
 653 
 654         if ((error != APM_NO_EVENTS) && (notified++ == 0))
 655                 apm_error("get_event", error);
 656 
 657         return 0;
 658 }
 659 
 660 static void send_event(apm_event_t event, apm_event_t undo)
     /* [previous][next][first][last][top][bottom][index][help] */
 661 {
 662         callback_list_t *       call;
 663         callback_list_t *       fix;
 664     
 665         for (call = callback_list; call != NULL; call = call->next) {
 666                 if (call->callback(event) && undo) {
 667                         for (fix = callback_list; fix != call; fix = fix->next)
 668                                 fix->callback(undo);
 669                         if (apm_bios_info.version > 0x100)
 670                                 apm_set_power_state(APM_STATE_REJECT);
 671                         return;
 672                 }
 673         }
 674 
 675         queue_event(event);
 676 }
 677 
 678 static void check_events(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 679 {
 680         apm_event_t     event;
 681 
 682         while ((event = get_event()) != 0) {
 683                 switch (event) {
 684                 case APM_SYS_STANDBY:
 685                 case APM_USER_STANDBY:
 686                         send_event(event, APM_STANDBY_RESUME);
 687                         if (standbys_pending <= 0)
 688                                 standby();
 689                         break;
 690 
 691                 case APM_USER_SUSPEND:
 692 #ifdef CONFIG_APM_IGNORE_USER_SUSPEND
 693                         apm_set_power_state(APM_STATE_REJECT);
 694                         break;
 695 #endif
 696                 case APM_SYS_SUSPEND:
 697                         send_event(event, APM_NORMAL_RESUME);
 698                         if (suspends_pending <= 0)
 699                                 suspend();
 700                         break;
 701 
 702                 case APM_NORMAL_RESUME:
 703                 case APM_CRITICAL_RESUME:
 704                 case APM_STANDBY_RESUME:
 705                         set_time();
 706                         send_event(event, 0);
 707                         break;
 708 
 709                 case APM_LOW_BATTERY:
 710                 case APM_POWER_STATUS_CHANGE:
 711                         send_event(event, 0);
 712                         break;
 713 
 714                 case APM_UPDATE_TIME:
 715                         set_time();
 716                         break;
 717 
 718                 case APM_CRITICAL_SUSPEND:
 719                         suspend();
 720                         break;
 721                 }
 722 #ifdef APM_DEBUG
 723                 if (event <= NR_APM_EVENT_NAME)
 724                         printk("APM BIOS received %s notify\n",
 725                                apm_event_name[event - 1]);
 726                 else
 727                         printk("APM BIOS received unknown event 0x%02x\n",
 728                                event);
 729 #endif
 730         }
 731 }
 732 
 733 static void do_apm_timer(unsigned long unused)
     /* [previous][next][first][last][top][bottom][index][help] */
 734 {
 735         int     err;
 736 
 737         static int      pending_count = 0;
 738 
 739         if (((standbys_pending > 0) || (suspends_pending > 0))
 740             && (apm_bios_info.version > 0x100)
 741             && (pending_count-- <= 0)) {
 742                 pending_count = 4;
 743 
 744                 err = apm_set_power_state(APM_STATE_BUSY);
 745                 if (err)
 746                         apm_error("busy", err);
 747         }
 748 
 749         if (!(((standbys_pending > 0) || (suspends_pending > 0))
 750               && (apm_bios_info.version == 0x100)))
 751                 check_events();
 752 
 753         init_timer(&apm_timer);
 754         apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
 755         add_timer(&apm_timer);
 756 }
 757 
 758 /* Called from sys_idle, must make sure apm_enabled. */
 759 int apm_do_idle(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 760 {
 761 #ifdef CONFIG_APM_CPU_IDLE
 762         unsigned short  error;
 763 
 764         if (!apm_enabled)
 765                 return 0;
 766 
 767         APM_SET_CPU_IDLE(error);
 768         if (error & 0xff)
 769                 return 0;
 770 
 771         clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
 772         return 1;
 773 #else
 774         return 0;
 775 #endif
 776 }
 777 
 778 /* Called from sys_idle, must make sure apm_enabled. */
 779 void apm_do_busy(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 780 {
 781 #ifdef CONFIG_APM_CPU_IDLE
 782         unsigned short  error;
 783 
 784         if (!apm_enabled)
 785                 return;
 786         
 787 #ifndef ALWAYS_CALL_BUSY
 788         if (!clock_slowed)
 789                 return;
 790 #endif
 791 
 792         APM_SET_CPU_BUSY(error);
 793 
 794         clock_slowed = 0;
 795 #endif
 796 }
 797 
 798 static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
     /* [previous][next][first][last][top][bottom][index][help] */
 799 {
 800         if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
 801                 printk("apm_bios: %s passed bad filp", func);
 802                 return 1;
 803         }
 804         return 0;
 805 }
 806 
 807 static int do_read(struct inode *inode, struct file *fp, char *buf, int count)
     /* [previous][next][first][last][top][bottom][index][help] */
 808 {
 809         struct apm_bios_struct *        as;
 810         int                     i;
 811         apm_event_t             event;
 812         struct wait_queue       wait = { current,       NULL };
 813 
 814         as = fp->private_data;
 815         if (check_apm_bios_struct(as, "read"))
 816                 return -EIO;
 817         if (count < sizeof(apm_event_t))
 818                 return -EINVAL;
 819         if (queue_empty(as)) {
 820                 if (fp->f_flags & O_NONBLOCK)
 821                         return -EAGAIN;
 822                 add_wait_queue(&process_list, &wait);
 823 repeat:
 824                 current->state = TASK_INTERRUPTIBLE;
 825                 if (queue_empty(as)
 826                     && !(current->signal & ~current->blocked)) {
 827                         schedule();
 828                         goto repeat;
 829                 }
 830                 current->state = TASK_RUNNING;
 831                 remove_wait_queue(&process_list, &wait);
 832         }
 833         i = count;
 834         while ((i >= sizeof(event)) && !queue_empty(as)) {
 835                 event = get_queued_event(as);
 836                 memcpy_tofs(buf, &event, sizeof(event));
 837                 buf += sizeof(event);
 838                 i -= sizeof(event);
 839         }
 840         if (i < count)
 841                 return count - i;
 842         if (current->signal & ~current->blocked)
 843                 return -ERESTARTSYS;
 844         return 0;
 845 }
 846 
 847 static int do_select(struct inode *inode, struct file *fp, int sel_type,
     /* [previous][next][first][last][top][bottom][index][help] */
 848                      select_table * wait)
 849 {
 850         struct apm_bios_struct *        as;
 851 
 852         as = fp->private_data;
 853         if (check_apm_bios_struct(as, "select"))
 854                 return 0;
 855         if (sel_type != SEL_IN)
 856                 return 0;
 857         if (!queue_empty(as))
 858                 return 1;
 859         select_wait(&process_list, wait);
 860         return 0;
 861 }
 862 
 863 static int do_ioctl(struct inode * inode, struct file *filp,
     /* [previous][next][first][last][top][bottom][index][help] */
 864                     u_int cmd, u_long arg)
 865 {
 866         struct apm_bios_struct *        as;
 867 
 868         as = filp->private_data;
 869         if (check_apm_bios_struct(as, "ioctl"))
 870                 return -EIO;
 871         switch (cmd) {
 872         case APM_IOC_STANDBY:
 873                 if (as->standbys_pending > 0) {
 874                         as->standbys_pending--;
 875                         standbys_pending--;
 876                         if (standbys_pending <= 0)
 877                                 standby();
 878                 }
 879                 break;
 880         case APM_IOC_SUSPEND:
 881                 if (as->suspends_pending > 0) {
 882                         as->suspends_pending--;
 883                         suspends_pending--;
 884                         if (suspends_pending <= 0)
 885                                 suspend();
 886                 }
 887                 break;
 888         default:
 889                 return -EINVAL;
 890         }
 891         return 0;
 892 }
 893 
 894 static void do_release(struct inode * inode, struct file * filp)
     /* [previous][next][first][last][top][bottom][index][help] */
 895 {
 896         struct apm_bios_struct *        as;
 897 
 898         as = filp->private_data;
 899         filp->private_data = NULL;
 900         if (check_apm_bios_struct(as, "release"))
 901                 return;
 902         if (as->standbys_pending > 0) {
 903                 standbys_pending -= as->standbys_pending;
 904                 if (standbys_pending <= 0)
 905                         standby();
 906         }
 907         if (as->suspends_pending > 0) {
 908                 suspends_pending -= as->suspends_pending;
 909                 if (suspends_pending <= 0)
 910                         suspend();
 911         }
 912         if (user_list == as)
 913                 user_list = as->next;
 914         else {
 915                 struct apm_bios_struct *        as1;
 916 
 917                 for (as1 = user_list;
 918                      (as1 != NULL) && (as1->next != as);
 919                      as1 = as1->next)
 920                         ;
 921                 if (as1 == NULL)
 922                         printk("apm_bios: filp not in user list");
 923                 else
 924                         as1->next = as->next;
 925         }
 926         kfree_s(as, sizeof(*as));
 927 }
 928 
 929 static int do_open(struct inode * inode, struct file * filp)
     /* [previous][next][first][last][top][bottom][index][help] */
 930 {
 931         struct apm_bios_struct *        as;
 932 
 933         as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL);
 934         if (as == NULL) {
 935                 printk("apm_bios: cannot allocate struct of size %d bytes",
 936                        sizeof(*as));
 937                 return -ENOMEM;
 938         }
 939         as->magic = APM_BIOS_MAGIC;
 940         as->event_tail = as->event_head = 0;
 941         as->suspends_pending = as->standbys_pending = 0;
 942         as->suser = suser();
 943         as->next = user_list;
 944         user_list = as;
 945         filp->private_data = as;
 946         return 0;
 947 }
 948 
 949 #ifdef CONFIG_PROC_FS
 950 int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
     /* [previous][next][first][last][top][bottom][index][help] */
 951 {
 952         char *          p;
 953         unsigned short  bx;
 954         unsigned short  cx;
 955         unsigned short  dx;
 956         unsigned short  error;
 957         unsigned short  ac_line_status = 0xff;
 958         unsigned short  battery_status = 0xff;
 959         unsigned short  battery_flag   = 0xff;
 960         int             percentage     = -1;
 961         int             time_units     = -1;
 962         char            *units         = "?";
 963 
 964         if (!apm_enabled)
 965                 return 0;
 966         p = buf;
 967 
 968         if (!(error = apm_get_power_status(&bx, &cx, &dx))) {
 969                 ac_line_status = (bx >> 8) & 0xff;
 970                 battery_status = bx & 0xff;
 971                 if ((cx & 0xff) != 0xff)
 972                         percentage = cx & 0xff;
 973 
 974                 if (apm_bios_info.version > 0x100) {
 975                         battery_flag = (cx >> 8) & 0xff;
 976                         if (dx != 0xffff) {
 977                                 if ((dx & 0x8000) == 0x8000) {
 978                                         units = "min";
 979                                         time_units = dx & 0x7ffe;
 980                                 } else {
 981                                         units = "sec";
 982                                         time_units = dx & 0x7fff;
 983                                 }
 984                         }
 985                 }
 986         }
 987         /* Arguments, with symbols from linux/apm_bios.h.  Information is
 988            from the Get Power Status (0x0a) call unless otherwise noted.
 989 
 990            0) Linux driver version (this will change if format changes)
 991            1) APM BIOS Version.  Usually 1.0 or 1.1.
 992            2) APM flags from APM Installation Check (0x00):
 993               bit 0: APM_16_BIT_SUPPORT
 994               bit 1: APM_32_BIT_SUPPORT
 995               bit 2: APM_IDLE_SLOWS_CLOCK
 996               bit 3: APM_BIOS_DISABLED
 997               bit 4: APM_BIOS_DISENGAGED
 998            3) AC line status
 999               0x00: Off-line
1000               0x01: On-line
1001               0x02: On backup power (APM BIOS 1.1 only)
1002               0xff: Unknown
1003            4) Battery status
1004               0x00: High
1005               0x01: Low
1006               0x02: Critical
1007               0x03: Charging
1008               0xff: Unknown
1009            5) Battery flag
1010               bit 0: High
1011               bit 1: Low
1012               bit 2: Critical
1013               bit 3: Charging
1014               bit 7: No system battery
1015               0xff: Unknown
1016            6) Remaining battery life (percentage of charge):
1017               0-100: valid
1018               -1: Unknown
1019            7) Remaining battery life (time units):
1020               Number of remaining minutes or seconds
1021               -1: Unknown
1022            8) min = minutes; sec = seconds */
1023             
1024         p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
1025                      driver_version,
1026                      (apm_bios_info.version >> 8) & 0xff,
1027                      apm_bios_info.version & 0xff,
1028                      apm_bios_info.flags,
1029                      ac_line_status,
1030                      battery_status,
1031                      battery_flag,
1032                      percentage,
1033                      time_units,
1034                      units);
1035 
1036         return p - buf;
1037 }
1038 #endif
1039 
1040 void apm_bios_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
1041 {
1042         unsigned short  bx;
1043         unsigned short  cx;
1044         unsigned short  dx;
1045         unsigned short  error;
1046         char *          power_stat;
1047         char *          bat_stat;
1048 
1049         if (apm_bios_info.version == 0) {
1050                 printk("APM BIOS not found.\n");
1051                 return;
1052         }
1053         printk("APM BIOS version %c.%c Flags 0x%02x (Driver version %s)\n",
1054                ((apm_bios_info.version >> 8) & 0xff) + '0',
1055                (apm_bios_info.version & 0xff) + '0',
1056                apm_bios_info.flags,
1057                driver_version);
1058         if ((apm_bios_info.flags & APM_32_BIT_SUPPORT) == 0) {
1059                 printk("    No 32 bit BIOS support\n");
1060                 return;
1061         }
1062 
1063         /*
1064          * Fix for the Compaq Contura 3/25c which reports BIOS version 0.1
1065          * but is reportedly a 1.0 BIOS.
1066          */
1067         if (apm_bios_info.version == 0x001)
1068                 apm_bios_info.version = 0x100;
1069 
1070         printk("    Entry %x:%lx cseg16 %x dseg %x",
1071                apm_bios_info.cseg, apm_bios_info.offset,
1072                apm_bios_info.cseg_16, apm_bios_info.dseg);
1073         if (apm_bios_info.version > 0x100)
1074                 printk(" cseg len %x, dseg len %x",
1075                        apm_bios_info.cseg_len, apm_bios_info.dseg_len);
1076         printk("\n");
1077 
1078         apm_bios_entry.offset = apm_bios_info.offset;
1079         apm_bios_entry.segment = APM_CS;
1080         set_base(gdt[APM_CS >> 3],
1081                  0xc0000000 + ((unsigned long)apm_bios_info.cseg << 4));
1082         set_base(gdt[APM_CS_16 >> 3],
1083                  0xc0000000 + ((unsigned long)apm_bios_info.cseg_16 << 4));
1084         set_base(gdt[APM_DS >> 3],
1085                  0xc0000000 + ((unsigned long)apm_bios_info.dseg << 4));
1086         if (apm_bios_info.version == 0x100) {
1087                 set_limit(gdt[APM_CS >> 3], 64 * 1024);
1088                 set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
1089                 set_limit(gdt[APM_DS >> 3], 64 * 1024);
1090         } else {
1091 #ifdef APM_RELAX_SEGMENTS
1092                 /* For ASUS motherboard, Award BIOS rev 110 (and others?) */
1093                 set_limit(gdt[APM_CS >> 3], 64 * 1024);
1094                 /* For some unknown machine. */
1095                 set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
1096                 /* For the DEC Hinote Ultra CT475 (and others?) */
1097                 set_limit(gdt[APM_DS >> 3], 64 * 1024);
1098 #else
1099                 set_limit(gdt[APM_CS >> 3], apm_bios_info.cseg_len);
1100                 set_limit(gdt[APM_CS_16 >> 3], 64 * 1024);
1101                 set_limit(gdt[APM_DS >> 3], apm_bios_info.dseg_len);
1102 #endif
1103                 apm_bios_info.version = 0x0101;
1104                 error = apm_driver_version(&apm_bios_info.version);
1105                 if (error != 0)
1106                         apm_bios_info.version = 0x100;
1107                 else {
1108                         apm_engage_power_management(0x0001);
1109                         printk( "    Connection version %d.%d\n",
1110                                 (apm_bios_info.version >> 8) & 0xff,
1111                                 apm_bios_info.version & 0xff );
1112                         apm_bios_info.version = 0x0101;
1113                 }
1114         }
1115 
1116         error = apm_get_power_status(&bx, &cx, &dx);
1117         if (error)
1118                 printk("    Power status not available\n");
1119         else {
1120                 switch ((bx >> 8) & 0xff) {
1121                 case 0: power_stat = "off line"; break;
1122                 case 1: power_stat = "on line"; break;
1123                 case 2: power_stat = "on backup power"; break;
1124                 default: power_stat = "unknown"; break;
1125                 }
1126                 switch (bx & 0xff) {
1127                 case 0: bat_stat = "high"; break;
1128                 case 1: bat_stat = "low"; break;
1129                 case 2: bat_stat = "critical"; break;
1130                 case 3: bat_stat = "charging"; break;
1131                 default: bat_stat = "unknown"; break;
1132                 }
1133                 printk("    AC %s, battery status %s, battery life ",
1134                        power_stat, bat_stat);
1135                 if ((cx & 0xff) == 0xff)
1136                         printk("unknown\n");
1137                 else
1138                         printk("%d%%\n", cx & 0xff);
1139                 if (apm_bios_info.version > 0x100) {
1140                         printk("    battery flag 0x%02x, battery life ",
1141                                (cx >> 8) & 0xff);
1142                         if (dx == 0xffff)
1143                                 printk("unknown\n");
1144                         else {
1145                                 if ((dx & 0x8000)) 
1146                                         printk("%d minutes\n", dx & 0x7ffe );
1147                                 else
1148                                         printk("%d seconds\n", dx & 0x7fff );
1149                         }
1150                 }
1151         }
1152 
1153 #ifdef CONFIG_APM_DO_ENABLE
1154         /*
1155          * This call causes my NEC UltraLite Versa 33/C to hang if it is
1156          * booted with PM disabled but not in the docking station.
1157          * Unfortunate ...
1158          */
1159         error = apm_enable_power_management();
1160         if (error)
1161                 apm_error("enable power management", error);
1162         if (error == APM_DISABLED)
1163                 return;
1164 #endif
1165 
1166         init_timer(&apm_timer);
1167         apm_timer.function = do_apm_timer;
1168         apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
1169         add_timer(&apm_timer);
1170 
1171         register_symtab(&apm_syms);
1172 
1173 #ifdef CONFIG_PROC_FS
1174         proc_register_dynamic(&proc_root, &apm_proc_entry);
1175 #endif
1176 
1177         misc_register(&apm_device);
1178 
1179         apm_enabled = 1;
1180 }

/* [previous][next][first][last][top][bottom][index][help] */