patch-2.1.131 linux/arch/i386/kernel/apm.c

Next file: linux/arch/i386/kernel/entry.S
Previous file: linux/arch/i386/config.in
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.130/linux/arch/i386/kernel/apm.c linux/arch/i386/kernel/apm.c
@@ -30,6 +30,7 @@
  * Feb 1998, Version 1.4
  * Aug 1998, Version 1.5
  * Sep 1998, Version 1.6
+ * Nov 1998, Version 1.7
  *
  * History:
  *    0.6b: first version in official kernel, Linux 1.3.46
@@ -60,6 +61,17 @@
  *         Fix OOPS at power off with no APM BIOS by Jan Echternach
  *                   <echter@informatik.uni-rostock.de>
  *         Stephen Rothwell
+ *    1.7: Modify driver's cached copy of the disabled/disengaged flags
+ *         to reflect current state of APM BIOS.
+ *         Chris Rankin <rankinc@bellsouth.net>
+ *         Reset interrupt 0 timer to 100Hz after suspend
+ *         Chad Miller <cmiller@surfsouth.com>
+ *         Add CONFIG_APM_IGNORE_SUSPEND_BOUNCE
+ *         Richard Gooch <rgooch@atnf.csiro.au>
+ *         Allow boot time disabling of APM
+ *         Make boot messages far less verbose by default
+ *         Make asm safer
+ *         Stephen Rothwell
  *
  * APM 1.1 Reference:
  *
@@ -167,7 +179,7 @@
  *
  * 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
+ * ?: 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
@@ -215,6 +227,19 @@
 #define APM_RELAX_SEGMENTS
 
 /*
+ * Define to re-initialize the interrupt 0 timer to 100 Hz after a suspend.
+ * This patched by Chad Miller <cmiller@surfsouth.com>, orig code by David 
+ * Chen <chen@ctpa04.mit.edu>
+ */
+#undef INIT_TIMER_AFTER_SUSPEND
+
+#ifdef INIT_TIMER_AFTER_SUSPEND
+#include <linux/timex.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#endif
+
+/*
  * Need to poll the APM BIOS every second
  */
 #define APM_CHECK_TIMEOUT	(HZ)
@@ -267,13 +292,15 @@
 
 static long			clock_cmos_diff;
 static int			got_clock_diff = 0;
+static int			debug = 0;
+static int			apm_disabled = 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.6";	/* no spaces */
+static char			driver_version[] = "1.7";	/* no spaces */
 
 #ifdef APM_DEBUG
 static char *	apm_event_name[] = {
@@ -393,7 +420,7 @@
 #	define APM_DO_RESTORE_SEGS
 #endif
 
-static inline u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
+static u8 apm_bios_call(u32 eax_in, u32 ebx_in, u32 ecx_in,
 	u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi)
 {
 	unsigned int	saved_fs;
@@ -404,13 +431,17 @@
 	APM_DO_CLI;
 	APM_DO_SAVE_SEGS;
 	__asm__ __volatile__(APM_DO_ZERO_SEGS
+		"pushl %%edi\n\t"
+		"pushl %%ebp\n\t"
 		"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
 		"setc %%al\n\t"
+		"popl %%ebp\n\t"
+		"popl %%edi\n\t"
 		APM_DO_POP_SEGS
-		:  "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
-		   "=S" (*esi)
+		: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx),
+		  "=S" (*esi)
 		: "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
-		: "ax", "bx", "cx", "dx", "si", "di", "bp", "memory", "cc");
+		: "memory", "cc");
 	APM_DO_RESTORE_SEGS;
 	__restore_flags(flags);
 	return *eax & 0xff;
@@ -420,7 +451,7 @@
  * This version only returns one value (usually an error code)
  */
 
-static inline u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
+static u8 apm_bios_call_simple(u32 eax_in, u32 ebx_in, u32 ecx_in,
 	u32 *eax)
 {
 	u8		error;
@@ -431,13 +462,22 @@
 	__save_flags(flags);
 	APM_DO_CLI;
 	APM_DO_SAVE_SEGS;
-	__asm__ __volatile__(APM_DO_ZERO_SEGS
-		"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
-		"setc %%bl\n\t"
-		APM_DO_POP_SEGS
-		: "=a" (*eax), "=b" (error)
-		: "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
-		: "ax", "bx", "cx", "dx", "si", "di", "bp", "memory", "cc");
+	{
+		int	cx, dx, si;
+
+		__asm__ __volatile__(APM_DO_ZERO_SEGS
+			"pushl %%edi\n\t"
+			"pushl %%ebp\n\t"
+			"lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t"
+			"setc %%bl\n\t"
+			"popl %%ebp\n\t"
+			"popl %%edi\n\t"
+			APM_DO_POP_SEGS
+			: "=a" (*eax), "=b" (error), "=c" (cx), "=d" (dx),
+			  "=S" (si)
+			: "a" (eax_in), "b" (ebx_in), "c" (ecx_in)
+			: "memory", "cc");
+	}
 	APM_DO_RESTORE_SEGS;
 	__restore_flags(flags);
 	return error;
@@ -470,7 +510,7 @@
 	return APM_SUCCESS;
 }
 
-static inline int set_power_state(u_short what, u_short state)
+static int set_power_state(u_short what, u_short state)
 {
 	u32	eax;
 
@@ -499,7 +539,6 @@
 #endif
 
 #ifdef CONFIG_APM_DO_ENABLE
-/* Called by apm_setup if apm_enabled will be true. */
 static int apm_enable_power_management(void)
 {
 	u32	eax;
@@ -508,6 +547,7 @@
 			(apm_bios_info.version > 0x100) ? 0x0001 : 0xffff,
 			1, &eax))
 		return (eax >> 8) & 0xff;
+	apm_bios_info.flags &= ~APM_BIOS_DISABLED;
 	return APM_SUCCESS;
 }
 #endif
@@ -574,9 +614,10 @@
 	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);
+		printk(KERN_NOTICE "apm: %s: %s\n", str, error_table[i].msg);
 	else
-		printk(KERN_NOTICE "apm_bios: %s: unknown error code %#2.2x\n", str, err);
+		printk(KERN_NOTICE "apm: %s: unknown error code %#2.2x\n",
+			str, err);
 }
 
 /* Called from console driver -- must make sure apm_enabled. */
@@ -629,7 +670,6 @@
 	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;
@@ -663,7 +703,7 @@
 			static int notified;
 
 			if (notified == 0) {
-			    printk( "apm_bios: an event queue overflowed\n" );
+			    printk(KERN_ERR "apm: an event queue overflowed\n");
 			    notified = 1;
 			}
 			as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
@@ -719,6 +759,18 @@
 	err = apm_set_power_state(APM_STATE_SUSPEND);
 	if (err)
 		apm_error("suspend", err);
+#ifdef INIT_TIMER_AFTER_SUSPEND
+	save_flags(flags);
+	cli();
+	/* set the clock to 100 Hz */
+	outb_p(0x34,0x43);		/* binary, mode 2, LSB/MSB, ch 0 */
+	udelay(10);
+	outb_p(LATCH & 0xff , 0x40);	/* LSB */
+	udelay(10);
+	outb(LATCH >> 8 , 0x40);	/* MSB */
+	udelay(10);
+	restore_flags(flags);
+#endif
 	set_time();
 }
 
@@ -771,24 +823,27 @@
 
 static void check_events(void)
 {
-	apm_event_t	event;
+	apm_event_t		event;
+#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
+	static unsigned long	last_resume = 0;
+	static int		did_resume = 0;
+#endif
 
 	while ((event = get_event()) != 0) {
 #ifdef APM_DEBUG
 		if (event <= NR_APM_EVENT_NAME)
-			printk(KERN_DEBUG "APM BIOS received %s notify\n",
+			printk(KERN_DEBUG "apm: received %s notify\n",
 			       apm_event_name[event - 1]);
 		else
-			printk(KERN_DEBUG "APM BIOS received unknown "
+			printk(KERN_DEBUG "apm: 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;
-			}
+			if (waiting_for_resume)
+				return;
 			waiting_for_resume = 1;
 #endif
 			send_event(event, APM_STANDBY_RESUME, NULL);
@@ -803,10 +858,13 @@
 			break;
 #endif
 		case APM_SYS_SUSPEND:
+#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
+			if (did_resume && ((jiffies - last_resume) < HZ))
+				break;
+#endif
 #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
-			if (waiting_for_resume) {
-			    return;
-			}
+			if (waiting_for_resume)
+				return;
 			waiting_for_resume = 1;
 #endif
 			send_event(event, APM_NORMAL_RESUME, NULL);
@@ -820,6 +878,10 @@
 #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND
 			waiting_for_resume = 0;
 #endif
+#ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE
+			last_resume = jiffies;
+			did_resume = 1;
+#endif
 			set_time();
 			send_event(event, 0, NULL);
 			break;
@@ -905,7 +967,7 @@
 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);
+		printk(KERN_ERR "apm: %s passed bad filp", func);
 		return 1;
 	}
 	return 0;
@@ -1041,7 +1103,7 @@
 		     as1 = as1->next)
 			;
 		if (as1 == NULL)
-			printk(KERN_ERR "apm_bios: filp not in user list");
+			printk(KERN_ERR "apm: filp not in user list");
 		else
 			as1->next = as->next;
 	}
@@ -1055,7 +1117,7 @@
 
 	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",
+		printk(KERN_ERR "apm: cannot allocate struct of size %d bytes",
 		       sizeof(*as));
 		return -ENOMEM;
 	}
@@ -1168,6 +1230,26 @@
 }
 #endif
 
+void __init apm_setup(char *str, int *dummy)
+{
+	int	invert;
+
+	while ((str != NULL) && (*str != '\0')) {
+		if (strncmp(str, "off", 3) == 0)
+			apm_disabled = 1;
+		if (strncmp(str, "on", 2) == 0)
+			apm_disabled = 0;
+		invert = (strncmp(str, "no-", 3) == 0);
+		if (invert)
+			str += 3;
+		if (strncmp(str, "debug", 5) == 0)
+			debug = !invert;
+		str = strchr(str, ',');
+		if (str != NULL)
+			str += strspn(str, ", \t");
+	}
+}
+
 void __init apm_bios_init(void)
 {
 	unsigned short	bx;
@@ -1178,23 +1260,18 @@
 	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");
+		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);
+	printk(KERN_INFO
+		"apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n",
+		((apm_bios_info.version >> 8) & 0xff),
+		(apm_bios_info.version & 0xff),
+		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");
+		printk(KERN_INFO "apm: no 32 bit BIOS support\n");
 		return;
 	}
 
@@ -1209,14 +1286,28 @@
 	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");
+	if (debug) {
+		printk(KERN_INFO "apm: 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");
+	}
+
+	if (apm_disabled) {
+		printk(KERN_NOTICE "apm: disabled on user request.\n");
+		return;
+	}
+#ifdef __SMP__
+	if (smp_num_cpus > 1) {
+		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
+		return;
+	}
+#endif
 
 	/*
 	 * Set up a segment that references the real mode segment 0x40
@@ -1253,74 +1344,78 @@
 		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.
+		/*
+		 * We only support BIOSs up to version 1.2
 		 */
-		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. */
+		if (apm_bios_info.version > 0x0102)
+			apm_bios_info.version = 0x0102;
+		if (apm_driver_version(&apm_bios_info.version) != 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)
+	if (debug) {
+		printk(KERN_INFO "apm: onnection 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 "apm: 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 "apm: AC %s, battery status %s, battery life ",
+			       power_stat, bat_stat);
+			if ((cx & 0xff) == 0xff)
 				printk("unknown\n");
-			else {
-				if ((dx & 0x8000))
-					printk("%d minutes\n", dx & 0x7ffe );
-				else
-					printk("%d seconds\n", dx & 0x7fff );
+			else
+				printk("%d%%\n", cx & 0xff);
+			if (apm_bios_info.version > 0x100) {
+				printk("apm: 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;
+	if (apm_bios_info.flags & APM_BIOS_DISABLED) {
+		/*
+		 * 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);
+			return;
+		}
+	}
 #endif
+	if (((apm_bios_info.flags & APM_BIOS_DISABLED) == 0)
+	    && (apm_bios_info.version > 0x0100)) {
+		if (apm_engage_power_management(0x0001) == APM_SUCCESS)
+			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
+	}
 
 	init_timer(&apm_timer);
 	apm_timer.function = do_apm_timer;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov