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

Next file: linux/arch/i386/kernel/bios32.c
Previous file: linux/arch/i386/kernel/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.12/linux/arch/i386/kernel/apm.c linux/arch/i386/kernel/apm.c
@@ -273,7 +273,6 @@
 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 *);
@@ -314,11 +313,9 @@
 static int			debug = 0;
 static int			apm_disabled = 0;
 
-static DECLARE_WAIT_QUEUE_HEAD(process_list);
+static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
 static struct apm_bios_struct *	user_list = NULL;
 
-static struct timer_list	apm_timer;
-
 static char			driver_version[] = "1.9";	/* no spaces */
 
 #ifdef APM_DEBUG
@@ -543,6 +540,50 @@
 	return set_power_state(0x0001, state);
 }
 
+/*
+ * If no process has been interested in this
+ * CPU for some time, we want to wake up the
+ * power management thread - we probably want
+ * to conserve power.
+ */
+#define HARD_IDLE_TIMEOUT (HZ/3)
+
+/* This should wake up kapmd and ask it to slow the CPU */
+#define powermanagement_idle()  do { } while (0)
+
+extern int hlt_counter;
+
+/*
+ * This is the idle thing.
+ */
+void apm_cpu_idle(void)
+{
+	unsigned int start_idle;
+
+	start_idle = jiffies;
+	while (1) {
+		if (!current->need_resched) {
+			if (jiffies - start_idle < HARD_IDLE_TIMEOUT) {
+				if (!current_cpu_data.hlt_works_ok)
+					continue;
+				if (hlt_counter)
+					continue;
+				asm volatile("sti ; hlt" : : : "memory");
+				continue;
+			}
+
+			/*
+			 * Ok, do some power management - we've been idle for too long
+			 */
+			powermanagement_idle();
+		}
+
+		schedule();
+		check_pgt_cache();
+		start_idle = jiffies;
+	}
+}
+
 void apm_power_off(void)
 {
 	/*
@@ -756,7 +797,7 @@
 			break;
 		}
 	}
-	wake_up_interruptible(&process_list);
+	wake_up_interruptible(&apm_waitqueue);
 	return 1;
 }
 
@@ -942,32 +983,39 @@
 	}
 }
 
-static void do_apm_timer(unsigned long unused)
+/*
+ * This is the APM thread main loop.
+ */
+static void apm_mainloop(void)
 {
-	int	err;
-
-	static int	pending_count = 0;
+	DECLARE_WAITQUEUE(wait, current);
+	apm_enabled = 1;
 
-	if (((standbys_pending > 0) || (suspends_pending > 0))
-	    && (apm_bios_info.version > 0x100)
-	    && (pending_count-- <= 0)) {
-		pending_count = 4;
+	add_wait_queue(&apm_waitqueue, &wait);
+	for (;;) {
+		static int pending_count = 0;
+		int err;
 
-		err = apm_set_power_state(APM_STATE_BUSY);
-		if (err)
-			apm_error("busy", err);
-	}
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(APM_CHECK_TIMEOUT);
 
-	if (!(((standbys_pending > 0) || (suspends_pending > 0))
-	      && (apm_bios_info.version == 0x100)))
-		check_events();
+		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);
+		}
 
-	init_timer(&apm_timer);
-	apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
-	add_timer(&apm_timer);
+		if (!(((standbys_pending > 0) || (suspends_pending > 0))
+		      && (apm_bios_info.version == 0x100)))
+			check_events();
+	}
 }
 
-/* Called from sys_idle, must make sure apm_enabled. */
+/* Called from cpu_idle, must make sure apm_enabled. */
 int apm_do_idle(void)
 {
 #ifdef CONFIG_APM_CPU_IDLE
@@ -986,7 +1034,7 @@
 #endif
 }
 
-/* Called from sys_idle, must make sure apm_enabled. */
+/* Called from cpu_idle, must make sure apm_enabled. */
 void apm_do_busy(void)
 {
 #ifdef CONFIG_APM_CPU_IDLE
@@ -1027,7 +1075,7 @@
 	if (queue_empty(as)) {
 		if (fp->f_flags & O_NONBLOCK)
 			return -EAGAIN;
-		add_wait_queue(&process_list, &wait);
+		add_wait_queue(&apm_waitqueue, &wait);
 repeat:
 		current->state = TASK_INTERRUPTIBLE;
 		if (queue_empty(as) && !signal_pending(current)) {
@@ -1035,7 +1083,7 @@
 			goto repeat;
 		}
 		current->state = TASK_RUNNING;
-		remove_wait_queue(&process_list, &wait);
+		remove_wait_queue(&apm_waitqueue, &wait);
 	}
 	i = count;
 	while ((i >= sizeof(event)) && !queue_empty(as)) {
@@ -1069,7 +1117,7 @@
 	as = fp->private_data;
 	if (check_apm_bios_struct(as, "select"))
 		return 0;
-	poll_wait(fp, &process_list, wait);
+	poll_wait(fp, &apm_waitqueue, wait);
 	if (!queue_empty(as))
 		return POLLIN | POLLRDNORM;
 	return 0;
@@ -1263,7 +1311,97 @@
 	return p - buf;
 }
 
-void __init apm_setup(char *str, int *dummy)
+static int apm(void *unused)
+{
+	unsigned short	bx;
+	unsigned short	cx;
+	unsigned short	dx;
+	unsigned short	error;
+	char *		power_stat;
+	char *		bat_stat;
+
+	strcpy(current->comm, "kapmd");
+	sigfillset(&current->blocked);
+
+	if (apm_bios_info.version > 0x100) {
+		/*
+		 * We only support BIOSs up to version 1.2
+		 */
+		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;
+		}
+	}
+	if (debug) {
+		printk(KERN_INFO "apm: 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 "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
+				printk("%d%%\n", cx & 0xff);
+			if (apm_bios_info.version > 0x100) {
+				printk(KERN_INFO
+				       "apm: battery flag 0x%02x, battery life ",
+				       (cx >> 8) & 0xff);
+				if (dx == 0xffff)
+					printk("unknown\n");
+				else
+					printk("%d %s\n", dx & 0x7fff,
+						(dx & 0x8000) ?
+						"minutes" : "seconds");
+			}
+		}
+	}
+
+#ifdef CONFIG_APM_DO_ENABLE
+	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 -1;
+		}
+	}
+#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;
+	}
+
+	apm_mainloop();
+	return 0;
+}
+
+static int __init apm_setup(char *str)
 {
 	int	invert;
 
@@ -1283,16 +1421,23 @@
 		if (str != NULL)
 			str += strspn(str, ", \t");
 	}
+	return 1;
 }
 
-void __init apm_bios_init(void)
+__setup("apm=", apm_setup);
+
+/*
+ * Just start the APM thread. We do NOT want to do APM BIOS
+ * calls from anything but the APM thread, if for no other reason
+ * than the fact that we don't trust the APM BIOS. This way,
+ * most common APM BIOS problems that lead to protection errors
+ * etc will have at least some level of being contained...
+ *
+ * In short, if something bad happens, at least we have a choice
+ * of just killing the apm thread..
+ */
+static int __init apm_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;
 
 	if (apm_bios_info.version == 0) {
@@ -1339,6 +1484,15 @@
 		return;
 	}
 
+#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 -1;
+	}
+#endif
+
 	/*
 	 * Set up a segment that references the real mode segment 0x40
 	 * that extends up to the end of page zero (that we have reserved).
@@ -1378,92 +1532,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
-		 */
-		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;
-		}
-	}
-	if (debug) {
-		printk(KERN_INFO "apm: 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 "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
-				printk("%d%%\n", cx & 0xff);
-			if (apm_bios_info.version > 0x100) {
-				printk(KERN_INFO
-				       "apm: battery flag 0x%02x, battery life ",
-				       (cx >> 8) & 0xff);
-				if (dx == 0xffff)
-					printk("unknown\n");
-				else
-					printk("%d %s\n", dx & 0x7fff,
-						(dx & 0x8000) ?
-						"minutes" : "seconds");
-			}
-		}
-	}
-
-#ifdef CONFIG_APM_DO_ENABLE
-	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;
-	apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
-	add_timer(&apm_timer);
 
 	ent = create_proc_entry("apm", 0, 0);
 	if (ent != NULL)
@@ -1471,5 +1539,7 @@
 
 	misc_register(&apm_device);
 
-	apm_enabled = 1;
+	kernel_thread(apm, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
 }
+
+module_init(apm_init)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)