diff options
Diffstat (limited to 'arch/i386/kernel/apm.c')
-rw-r--r-- | arch/i386/kernel/apm.c | 345 |
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(¤t->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) |