[LWN Logo]

To:     linux-kernel@vger.rutgers.edu
Subject: [PATCH] 2.3.37 Make power down reliable on SMP
Date:   Sat, 08 Jan 2000 03:17:23 +1100
From:   Stephen Rothwell <sfr@linuxcare.com>

Hi All,

Something to try out on your SMP boxes ...

There is a particularly hackish hack in the middle of this patch
that is there purely to make the power off code only run on
CPU 0 of an SMP machine.

Comments welcome.

Cheers,
Stephen
--
Stephen Rothwell                    sfr@linuxcare.com
http://linuxcare.com.au/sfr/

diff -ruN 2.3.37/arch/i386/kernel/apm.c 2.3.37-APM/arch/i386/kernel/apm.c
--- 2.3.37/arch/i386/kernel/apm.c	Fri Jan  7 10:19:12 2000
+++ 2.3.37-APM/arch/i386/kernel/apm.c	Sat Jan  8 03:11:02 2000
@@ -35,6 +35,7 @@
  * Jan 1999, Version 1.9
  * Oct 1999, Version 1.10
  * Nov 1999, Version 1.11
+ * Jan 2000, Version 1.12
  *
  * History:
  *    0.6b: first version in official kernel, Linux 1.3.46
@@ -110,6 +111,11 @@
  *         (reported by Panos Katsaloulis <teras@writeme.com>).
  *         Real mode power off patch (Walter Hofmann
  *         <Walter.Hofmann@physik.stud.uni-erlangen.de>).
+ *   1.12: Remove CONFIG_SMP as the compiler will optimize
+ *         the code away anyway (smp_num_cpus == 1 in UP) 
+ *         noted by Artur Skawina <skawina@geocities.com>.
+ *         Make power off under SMP work again.
+ *         Fix thinko with initial engaging of BIOS.
  *
  * APM 1.1 Reference:
  *
@@ -285,7 +291,7 @@
 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
 static struct apm_bios_struct *	user_list = NULL;
 
-static char			driver_version[] = "1.11";	/* no spaces */
+static char			driver_version[] = "1.12";	/* no spaces */
 
 static char *	apm_event_name[] = {
 	"system standby",
@@ -512,6 +518,7 @@
 	}
 }
 
+#if 0
 extern int hlt_counter;
 
 /*
@@ -556,6 +563,15 @@
 	}
 }
 #endif
+#endif
+
+#ifdef CONFIG_SMP
+static int apm_magic(void * unused)
+{
+	while (1)
+		schedule();
+}
+#endif
 
 static void apm_power_off(void)
 {
@@ -567,6 +583,16 @@
 	 * they are doing because they booted with the smp-power-off
 	 * kernel option.
 	 */
+#ifdef CONFIG_SMP
+	if (smp_hack == 2) {
+		/* Many bioses don't like being called from CPU != 0 */
+		while (cpu_number_map[smp_processor_id()] != 0) {
+			kernel_thread(apm_magic, NULL,
+				CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
+			schedule();
+		}
+	}
+#endif
 	if (apm_enabled || (smp_hack == 2)) {
 #ifdef CONFIG_APM_REAL_MODE_POWER_OFF
 		unsigned char	po_bios_call[] = {
@@ -586,18 +612,21 @@
 	}
 }
 
-#ifdef CONFIG_APM_DO_ENABLE
-static int __init apm_enable_power_management(void)
+static int apm_enable_power_management(int enable)
 {
 	u32	eax;
 
+	if ((enable == 0) && (apm_bios_info.flags & APM_BIOS_DISENGAGED))
+		return APM_NOT_ENGAGED;
 	if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL,
-			1, &eax))
+			enable, &eax))
 		return (eax >> 8) & 0xff;
-	apm_bios_info.flags &= ~APM_BIOS_DISABLED;
+	if (enable)
+		apm_bios_info.flags &= ~APM_BIOS_DISABLED;
+	else
+		apm_bios_info.flags |= APM_BIOS_DISABLED;
 	return APM_SUCCESS;
 }
-#endif
 
 static int apm_get_power_status(u_short *status, u_short *bat, u_short *life)
 {
@@ -645,12 +674,21 @@
 }
 #endif
 
-static int __init apm_engage_power_management(u_short device)
+static int apm_engage_power_management(u_short device, int enable)
 {
 	u32	eax;
 
-	if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, 1, &eax))
+	if ((enable == 0) && (device == APM_DEVICE_ALL)
+	    && (apm_bios_info.flags & APM_BIOS_DISABLED))
+		return APM_DISABLED;
+	if (apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable, &eax))
 		return (eax >> 8) & 0xff;
+	if (device == APM_DEVICE_ALL) {
+		if (enable)
+			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
+		else
+			apm_bios_info.flags |= APM_BIOS_DISENGAGED;
+	}
 	return APM_SUCCESS;
 }
 
@@ -987,6 +1025,10 @@
 static void apm_mainloop(void)
 {
 	DECLARE_WAITQUEUE(wait, current);
+
+	if (smp_num_cpus > 1)
+		return;
+
 	apm_enabled = 1;
 
 	add_wait_queue(&apm_waitqueue, &wait);
@@ -1358,17 +1400,20 @@
 		 * is booted with PM disabled but not in the docking station.
 		 * Unfortunate ...
 		 */
-		error = apm_enable_power_management();
+		error = apm_enable_power_management(1);
 		if (error) {
 			apm_error("enable power management", error);
 			return -1;
 		}
 	}
 #endif
-	if (((apm_bios_info.flags & APM_BIOS_DISENGAGED) == 0)
+	if ((apm_bios_info.flags & APM_BIOS_DISENGAGED)
 	    && (apm_bios_info.version > 0x0100)) {
-		if (apm_engage_power_management(0x0001) == APM_SUCCESS)
-			apm_bios_info.flags &= ~APM_BIOS_DISENGAGED;
+		error = apm_engage_power_management(APM_DEVICE_ALL, 1);
+		if (error) {
+			apm_error("engage power management", error);
+			return -1;
+		}
 	}
 
 	/* Install our power off handler.. */
@@ -1541,20 +1586,19 @@
 	}
 #endif
 
-#ifdef CONFIG_SMP
+	kernel_thread(apm, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
+
 	if (smp_num_cpus > 1) {
 		printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n");
 		if (smp_hack)
 			smp_hack = 2;
 		APM_INIT_ERROR_RETURN;
 	}
-#endif
 
 	create_proc_info_entry("apm", 0, 0, apm_get_info);
 
 	misc_register(&apm_device);
 
-	kernel_thread(apm, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
 	return 0;
 }
 

-
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/