[LWN Logo]

To:	linux-kernel@vger.rutgers.edu
Subject: New APM patch
Date:	Sun, 06 Jun 1999 18:22:04 +1000
From:	Stephen Rothwell <Stephen.Rothwell@canb.auug.org.au>

Hi all,

I have finally got some time to APM updates again so here goes.
This first one is against 2.2.10 -pre2 but should patch cleanly
against 2.2.9 and 2.3.X.  It does the following:
	- Reset time across standby.  (this is apparently
	  needed by some systems.)
	- Allow more inititialisation on SMP systems - hopefully
	  this will allow them to power down.
	- Remove CONFIG_APM_POWER_OFF and make it boot time
	  configurable (default on).
	- Make debug only a boot time parameter (remove APM_DEBUG).
	- Try to blank all devices on any error.
and some cleanups.

Please try it out (as I don;t have the problems fixed, I cannot
test these changes.

In case it get mangled by the mailing list, the patch is also
available from my web page (see below) - as will its updates.

Cheers,
Stephen
--
Stephen Rothwell                    Stephen.Rothwell@canb.auug.org.au
http://www.canb.auug.org.au/~sfr/

diff -ru linux-2.2.10-pre2/Documentation/Configure.help linux-2.2.10-pre2-APM/Documentation/Configure.help
--- linux-2.2.10-pre2/Documentation/Configure.help	Sun Jun  6 17:33:36 1999
+++ linux-2.2.10-pre2-APM/Documentation/Configure.help	Sun Jun  6 16:07:40 1999
@@ -8722,9 +8722,15 @@
   APM is a BIOS specification for saving power using several different
   techniques. This is mostly useful for battery powered laptops with
   APM compliant BIOSes. If you say Y here, the system time will be
-  reset after a USER RESUME operation, the /proc/apm device will
-  provide battery status information, and user-space programs will
-  receive notification of APM "events" (e.g., battery status change).
+  reset after a RESUME operation, the /proc/apm device will provide
+  battery status information, and user-space programs will receive
+  notification of APM "events" (e.g., battery status change).
+
+  If you select "Y" here, you can disable actual use of the APM
+  BIOS by passing the "apm=off" option to the kernel at boot time.
+
+  Note that the APM support is almost completely disabled for
+  machines with more than one CPU.
 
   Supporting software is available; for more information, read the
   Battery Powered Linux mini-HOWTO, available via FTP (user:
@@ -8737,9 +8743,7 @@
   This driver does not support the TI 4000M TravelMate and the ACER
   486/DX4/75 because they don't have compliant BIOSes. Many "green"
   desktop machines also don't have compliant BIOSes, and this driver
-  will cause those machines to panic during the boot phase (typically,
-  these machines are using a data segment of 0040, which is reserved
-  for the Linux kernel). 
+  may cause those machines to panic during the boot phase.
 
   If you are running Linux on a laptop, you may also want to read the
   Linux Laptop home page on the WWW at
@@ -8815,18 +8819,6 @@
   option doesn't work for all laptops -- it might not turn off your
   backlight at all, or it might print a lot of errors to the console,
   especially if you are using gpm.
-
-Power off on shutdown 
-CONFIG_APM_POWER_OFF
-  Enable the ability to power off the computer after the Linux kernel
-  is halted. You will need software (e.g., a suitable version of the
-  halt(8) command ("man 8 halt")) to cause the computer to power down.
-  Recent versions of the sysvinit package available from
-  ftp://metalab.unc.edu/pub/Linux/system/daemons/init/ (user:
-  anonymous) contain support for this ("halt -p" shuts down Linux and
-  powers off the computer, if executed from runlevel 0). As with the
-  other APM options, this option may not work reliably with some APM
-  BIOS implementations.
 
 Ignore multiple suspend/standby events
 CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
diff -ru linux-2.2.10-pre2/arch/i386/config.in linux-2.2.10-pre2-APM/arch/i386/config.in
--- linux-2.2.10-pre2/arch/i386/config.in	Sat May 15 17:45:58 1999
+++ linux-2.2.10-pre2-APM/arch/i386/config.in	Sun Jun  6 17:31:27 1999
@@ -105,7 +105,6 @@
   bool '   Enable PM at boot time' CONFIG_APM_DO_ENABLE
   bool '   Make CPU Idle calls when idle' CONFIG_APM_CPU_IDLE
   bool '   Enable console blanking using APM' CONFIG_APM_DISPLAY_BLANK
-  bool '   Power off on shutdown' CONFIG_APM_POWER_OFF
   bool '   Ignore multiple suspend' CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
   bool '   Ignore multiple suspend/resume cycles' CONFIG_APM_IGNORE_SUSPEND_BOUNCE
   bool '   RTC stores time in GMT' CONFIG_APM_RTC_IS_GMT
diff -ru linux-2.2.10-pre2/arch/i386/kernel/apm.c linux-2.2.10-pre2-APM/arch/i386/kernel/apm.c
--- linux-2.2.10-pre2/arch/i386/kernel/apm.c	Fri Jan 15 17:57:25 1999
+++ linux-2.2.10-pre2-APM/arch/i386/kernel/apm.c	Sun Jun  6 16:38:45 1999
@@ -1,6 +1,6 @@
 /* -*- linux-c -*-
  * APM BIOS driver for Linux
- * Copyright 1994-1998 Stephen Rothwell
+ * Copyright 1994-1999 Stephen Rothwell
  *                     (Stephen.Rothwell@canb.auug.org.au)
  * Development of this driver was funded by NEC Australia P/L
  *	and NEC Corporation
@@ -33,6 +33,7 @@
  * Nov 1998, Version 1.7
  * Jan 1999, Version 1.8
  * Jan 1999, Version 1.9
+ * Jun 1999, Version 1.10
  *
  * History:
  *    0.6b: first version in official kernel, Linux 1.3.46
@@ -90,6 +91,12 @@
  *         Use CONFIG_SMP instead of __SMP__
  *         Ignore BOUNCES for three seconds.
  *         Stephen Rothwell
+ *   1.10: Reset time across standby.
+ *         Allow more inititialisation on SMP.
+ *         Remove CONFIG_APM_POWER_OFF and make it boot time
+ *         configurable (default on).
+ *         Make debug only a boot time parameter (remove APM_DEBUG).
+ *         Try to blank all devices on any error.
  *
  * APM 1.1 Reference:
  *
@@ -144,51 +151,14 @@
  */
 #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.
+/*
+ * See Documentation/Config.help for the configuration options.
  *
+ * Various options can be changed at boot time as follows:
+ *	apm=on/off			enable/disable APM
+ *	    [no-]debug			log some debugging messages
+ *	    [no-]power-off		power off on shutdown
+ *	    [no-]smp-power-off		allow power off even for SMP
  */
 
 /* KNOWN PROBLEM MACHINES:
@@ -207,11 +177,6 @@
  */
 
 /*
- * 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.
  */
@@ -266,27 +231,6 @@
 		__asm__ __volatile__("movl %%" #seg ",%0" : "=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);
-
-static int	apm_get_info(char *, char **, off_t, int, int);
-
-extern int	apm_register_callback(int (*)(apm_event_t));
-extern void	apm_unregister_callback(int (*)(apm_event_t));
-
-/*
  * Local variables
  */
 static asmlinkage struct {
@@ -313,15 +257,15 @@
 #endif
 static int			debug = 0;
 static int			apm_disabled = 0;
+static int			power_off_enabled = 1;
 
 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.9";	/* no spaces */
+static char			driver_version[] = "1.10";	/* no spaces */
 
-#ifdef APM_DEBUG
 static char *	apm_event_name[] = {
 	"system standby",
 	"system suspend",
@@ -338,28 +282,6 @@
 };
 #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);
@@ -502,7 +424,7 @@
 	return error;
 }
 
-static int apm_driver_version(u_short *val)
+static int __init apm_driver_version(u_short *val)
 {
 	u32	eax;
 
@@ -543,7 +465,7 @@
 	return set_power_state(0x0001, state);
 }
 
-void apm_power_off(void)
+void apm_power_off(int force)
 {
 	/*
 	 * smp_hack == 2 means that we would have enabled APM support
@@ -553,7 +475,8 @@
 	 * they are doing because they booted with the smp-power-off
 	 * kernel option.
 	 */
-	if (apm_enabled || (smp_hack == 2))
+	if ((apm_enabled || (smp_hack == 2))
+	    && (power_off_enabled || force))
 		(void) apm_set_power_state(APM_STATE_OFF);
 }
 
@@ -565,7 +488,7 @@
 
 	/* Blank the first display device */
 	error = set_power_state(0x0100, state);
-	if (error == APM_BAD_DEVICE)
+	if (error != APM_SUCCESS)
 		/* try to blank them all instead */
 		error = set_power_state(0x01ff, state);
 	return error;
@@ -733,10 +656,8 @@
 		if (as->event_head == as->event_tail) {
 			static int notified;
 
-			if (notified == 0) {
+			if (notified++ == 0)
 			    printk(KERN_ERR "apm: an event queue overflowed\n");
-			    notified = 1;
-			}
 			as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
 		}
 		as->events[as->event_head] = event;
@@ -764,21 +685,19 @@
 {
 	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);
+	if (got_clock_diff) {	/* Must know time zone in order to set clock */
+		save_flags(flags);
+		cli();
+		CURRENT_TIME = get_cmos_time() + clock_cmos_diff;
+		restore_flags(flags);
+	}
 }
 
-static void suspend(void)
+static void get_time_diff(void)
 {
+#ifndef CONFIG_APM_RTC_IS_GMT
 	unsigned long	flags;
-	int		err;
 
-#ifndef CONFIG_APM_RTC_IS_GMT
 	/*
 	 * Estimate time zone so that set_time can update the clock
 	 */
@@ -789,7 +708,14 @@
 	got_clock_diff = 1;
 	restore_flags(flags);
 #endif
+}
+
+static void suspend(void)
+{
+	unsigned long	flags;
+	int		err;
 
+	get_time_diff();
 	err = apm_set_power_state(APM_STATE_SUSPEND);
 	if (err)
 		apm_error("suspend", err);
@@ -812,9 +738,11 @@
 {
 	int	err;
 
+	get_time_diff();
 	err = apm_set_power_state(APM_STATE_STANDBY);
 	if (err)
 		apm_error("standby", err);
+	set_time();	/* should not be necessary ... */
 }
 
 static apm_event_t get_event(void)
@@ -864,14 +792,14 @@
 #endif
 
 	while ((event = get_event()) != 0) {
-#ifdef APM_DEBUG
-		if (event <= NR_APM_EVENT_NAME)
-			printk(KERN_DEBUG "apm: received %s notify\n",
-			       apm_event_name[event - 1]);
-		else
-			printk(KERN_DEBUG "apm: received unknown "
-			       "event 0x%02x\n", event);
-#endif
+		if (debug) {
+			if (event <= NR_APM_EVENT_NAME)
+				printk(KERN_DEBUG "apm: received %s notify\n",
+				       apm_event_name[event - 1]);
+			else
+				printk(KERN_DEBUG "apm: received unknown "
+				       "event 0x%02x\n", event);
+		}
 #ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
 		if (ignore_bounce
 		    && ((jiffies - last_resume) > BOUNCE_INTERVAL))
@@ -1277,6 +1205,8 @@
 			str += 3;
 		if (strncmp(str, "debug", 5) == 0)
 			debug = !invert;
+		if (strncmp(str, "power-off", 9) == 0)
+			power_off_enabled = !invert;
 		if (strncmp(str, "smp-power-off", 13) == 0)
 			smp_hack = !invert;
 		str = strchr(str, ',');
@@ -1285,6 +1215,27 @@
 	}
 }
 
+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
+};
+
 void __init apm_bios_init(void)
 {
 	unsigned short	bx;
@@ -1378,14 +1329,6 @@
 			(apm_bios_info.dseg_len - 1) & 0xffff);
 	}
 #endif
-#ifdef CONFIG_SMP
-	if (smp_num_cpus > 1) {
-		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
-		if (smp_hack)
-			smp_hack = 2;
-		return;
-	}
-#endif
 	if (apm_bios_info.version > 0x100) {
 		/*
 		 * We only support BIOSs up to version 1.2
@@ -1459,6 +1402,14 @@
 		if (apm_engage_power_management(0x0001) == APM_SUCCESS)
 			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
 	}
+#ifdef CONFIG_SMP
+	if (smp_num_cpus > 1) {
+		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
+		if (smp_hack)
+			smp_hack = 2;
+		return;
+	}
+#endif
 
 	init_timer(&apm_timer);
 	apm_timer.function = do_apm_timer;
diff -ru linux-2.2.10-pre2/arch/i386/kernel/process.c linux-2.2.10-pre2-APM/arch/i386/kernel/process.c
--- linux-2.2.10-pre2/arch/i386/kernel/process.c	Sat May 15 17:46:06 1999
+++ linux-2.2.10-pre2-APM/arch/i386/kernel/process.c	Sun Jun  6 15:50:42 1999
@@ -31,7 +31,7 @@
 #include <linux/smp.h>
 #include <linux/reboot.h>
 #include <linux/init.h>
-#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF)
+#if defined(CONFIG_APM)
 #include <linux/apm_bios.h>
 #endif
 
@@ -52,11 +52,6 @@
 
 asmlinkage void ret_from_fork(void) __asm__("ret_from_fork");
 
-#ifdef CONFIG_APM
-extern int  apm_do_idle(void);
-extern void apm_do_busy(void);
-#endif
-
 static int hlt_counter=0;
 
 #define HARD_IDLE_TIMEOUT (HZ / 3)
@@ -386,8 +381,8 @@
 
 void machine_power_off(void)
 {
-#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF)
-	apm_power_off();
+#if defined(CONFIG_APM)
+	apm_power_off(0);
 #endif
 }
 
diff -ru linux-2.2.10-pre2/arch/m68k/kernel/process.c linux-2.2.10-pre2-APM/arch/m68k/kernel/process.c
--- linux-2.2.10-pre2/arch/m68k/kernel/process.c	Sat May 15 17:46:06 1999
+++ linux-2.2.10-pre2-APM/arch/m68k/kernel/process.c	Sun Jun  6 15:51:12 1999
@@ -87,9 +87,6 @@
 
 void machine_power_off(void)
 {
-#if defined(CONFIG_APM) && defined(CONFIG_APM_POWER_OFF)
-	apm_set_power_state(APM_STATE_OFF);
-#endif
 }
 
 void show_regs(struct pt_regs * regs)
diff -ru linux-2.2.10-pre2/drivers/char/sysrq.c linux-2.2.10-pre2-APM/drivers/char/sysrq.c
--- linux-2.2.10-pre2/drivers/char/sysrq.c	Thu Nov 26 12:25:17 1998
+++ linux-2.2.10-pre2-APM/drivers/char/sysrq.c	Sun Jun  6 15:55:34 1999
@@ -21,13 +21,12 @@
 #include <linux/kbd_kern.h>
 #include <linux/quotaops.h>
 #include <linux/smp_lock.h>
-
-#include <asm/ptrace.h>
-
 #ifdef CONFIG_APM
 #include <linux/apm_bios.h>
 #endif
 
+#include <asm/ptrace.h>
+
 extern void wakeup_bdflush(int);
 extern void reset_vc(unsigned int);
 extern int console_loglevel;
@@ -85,7 +84,7 @@
 #ifdef CONFIG_APM
 	case 'o':					    /* O -- power off */
 		printk("Power off\n");
-		apm_power_off();
+		apm_power_off(1);
 		break;
 #endif
 	case 's':					    /* S -- emergency sync */
diff -ru linux-2.2.10-pre2/include/linux/apm_bios.h linux-2.2.10-pre2-APM/include/linux/apm_bios.h
--- linux-2.2.10-pre2/include/linux/apm_bios.h	Sun Nov 29 12:18:54 1998
+++ linux-2.2.10-pre2-APM/include/linux/apm_bios.h	Sun Jun  6 15:44:33 1999
@@ -3,7 +3,7 @@
 
 /*
  * Include file for the interface to an APM BIOS
- * Copyright 1994-1998 Stephen Rothwell (Stephen.Rothwell@canb.auug.org.au)
+ * Copyright 1994-1999 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
@@ -82,7 +82,10 @@
 extern int		apm_register_callback(int (*callback)(apm_event_t));
 extern void		apm_unregister_callback(int (*callback)(apm_event_t));
 
-extern void		apm_power_off(void);
+extern int		apm_do_idle(void);
+extern void		apm_do_busy(void);
+
+extern void		apm_power_off(int);
 extern int		apm_display_blank(void);
 extern int		apm_display_unblank(void);
 
@@ -112,7 +115,7 @@
 #define APM_USER_STANDBY	0x0009
 #define APM_USER_SUSPEND	0x000a
 #define APM_STANDBY_RESUME	0x000b
-#define APM_CAPABILITY_CHANGE   0x000c
+#define APM_CAPABILITY_CHANGE	0x000c
 
 /*
  * Error codes
@@ -128,7 +131,7 @@
 #define APM_BAD_DEVICE		0x09
 #define APM_BAD_PARAM		0x0a
 #define APM_NOT_ENGAGED		0x0b
-#define APM_BAD_FUNCTION        0x0c
+#define APM_BAD_FUNCTION	0x0c
 #define APM_RESUME_DISABLED	0x0d
 #define APM_BAD_STATE		0x60
 #define APM_NO_EVENTS		0x80

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/