summaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/apm.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/kernel/apm.c')
-rw-r--r--arch/i386/kernel/apm.c345
1 files changed, 223 insertions, 122 deletions
diff --git a/arch/i386/kernel/apm.c b/arch/i386/kernel/apm.c
index 7931e8df8..3bafdfcfc 100644
--- a/arch/i386/kernel/apm.c
+++ b/arch/i386/kernel/apm.c
@@ -273,7 +273,6 @@ 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 *);
@@ -289,7 +288,7 @@ extern void apm_unregister_callback(int (*)(apm_event_t));
/*
* Local variables
*/
-static asmlinkage struct {
+static struct {
unsigned long offset;
unsigned short segment;
} apm_bios_entry;
@@ -314,11 +313,9 @@ static int got_clock_diff = 0;
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 @@ static int apm_set_power_state(u_short state)
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 @@ static int queue_event(apm_event_t event, struct apm_bios_struct *sender)
break;
}
}
- wake_up_interruptible(&process_list);
+ wake_up_interruptible(&apm_waitqueue);
return 1;
}
@@ -942,15 +983,14 @@ static void check_events(void)
}
}
-static void do_apm_timer(unsigned long unused)
+static void apm_event_handler(void)
{
- int err;
-
- static int pending_count = 0;
+ static int pending_count = 0;
if (((standbys_pending > 0) || (suspends_pending > 0))
&& (apm_bios_info.version > 0x100)
&& (pending_count-- <= 0)) {
+ int err;
pending_count = 4;
err = apm_set_power_state(APM_STATE_BUSY);
@@ -961,14 +1001,9 @@ static void do_apm_timer(unsigned long unused)
if (!(((standbys_pending > 0) || (suspends_pending > 0))
&& (apm_bios_info.version == 0x100)))
check_events();
-
- init_timer(&apm_timer);
- apm_timer.expires = APM_CHECK_TIMEOUT + jiffies;
- add_timer(&apm_timer);
}
-/* Called from sys_idle, must make sure apm_enabled. */
-int apm_do_idle(void)
+static int apm_do_idle(void)
{
#ifdef CONFIG_APM_CPU_IDLE
u32 dummy;
@@ -979,30 +1014,74 @@ int apm_do_idle(void)
if (apm_bios_call_simple(0x5305, 0, 0, &dummy))
return 0;
+#ifdef ALWAYS_CALL_BUSY
+ clock_slowed = 1;
+#else
clock_slowed = (apm_bios_info.flags & APM_IDLE_SLOWS_CLOCK) != 0;
+#endif
return 1;
#else
return 0;
#endif
}
-/* Called from sys_idle, must make sure apm_enabled. */
-void apm_do_busy(void)
+static void apm_do_busy(void)
{
#ifdef CONFIG_APM_CPU_IDLE
u32 dummy;
- if (apm_enabled
-#ifndef ALWAYS_CALL_BUSY
- && clock_slowed
-#endif
- ) {
+ if (clock_slowed) {
(void) apm_bios_call_simple(0x5306, 0, 0, &dummy);
clock_slowed = 0;
}
#endif
}
+/*
+ * This is the APM thread main loop.
+ *
+ * Check whether we're the only running process to
+ * decide if we should just power down.
+ *
+ * Do this by checking the runqueue: if we're the
+ * only one, then the current process run_list will
+ * have both prev and next pointing to the same
+ * entry (the true idle process)
+ */
+#define system_idle() (current->run_list.next == current->run_list.prev)
+
+static void apm_mainloop(void)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ apm_enabled = 1;
+
+ add_wait_queue(&apm_waitqueue, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ for (;;) {
+ /* Nothing to do, just sleep for the timeout */
+ schedule_timeout(APM_CHECK_TIMEOUT);
+
+ /*
+ * Ok, check all events, check for idle (and mark us sleeping
+ * so as not to count towards the load average)..
+ */
+ current->state = TASK_INTERRUPTIBLE;
+ apm_event_handler();
+ if (!system_idle())
+ continue;
+ if (apm_do_idle()) {
+ unsigned long start = jiffies;
+ do {
+ apm_do_idle();
+ if (jiffies - start > APM_CHECK_TIMEOUT)
+ break;
+ } while (system_idle());
+ apm_do_busy();
+ apm_event_handler();
+ }
+ }
+}
+
static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func)
{
if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
@@ -1027,15 +1106,15 @@ static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos)
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;
+ set_current_state(TASK_INTERRUPTIBLE);
if (queue_empty(as) && !signal_pending(current)) {
schedule();
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 +1148,7 @@ static unsigned int do_poll(struct file *fp, poll_table * wait)
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 +1342,97 @@ int apm_get_info(char *buf, char **start, off_t fpos, int length, int dummy)
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_DISENGAGED) == 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 +1452,23 @@ void __init apm_setup(char *str, int *dummy)
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 +1515,15 @@ void __init apm_bios_init(void)
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 +1563,6 @@ void __init apm_bios_init(void)
(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 +1570,7 @@ void __init apm_bios_init(void)
misc_register(&apm_device);
- apm_enabled = 1;
+ kernel_thread(apm, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
}
+
+module_init(apm_init)