diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-24 00:12:35 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-24 00:12:35 +0000 |
commit | 482368b1a8e45430672c58c9a42e7d2004367126 (patch) | |
tree | ce2a1a567d4d62dee7c2e71a46a99cf72cf1d606 /arch/i386/kernel/apm.c | |
parent | e4d0251c6f56ab2e191afb70f80f382793e23f74 (diff) |
Merge with 2.3.47. Guys, this is buggy as shit. You've been warned.
Diffstat (limited to 'arch/i386/kernel/apm.c')
-rw-r--r-- | arch/i386/kernel/apm.c | 416 |
1 files changed, 284 insertions, 132 deletions
diff --git a/arch/i386/kernel/apm.c b/arch/i386/kernel/apm.c index 81a813b05..4ec5e7993 100644 --- a/arch/i386/kernel/apm.c +++ b/arch/i386/kernel/apm.c @@ -1,6 +1,6 @@ /* -*- linux-c -*- * APM BIOS driver for Linux - * Copyright 1994-1999 Stephen Rothwell (sfr@linuxcare.com) + * Copyright 1994-2000 Stephen Rothwell (sfr@linuxcare.com) * * Initial development of this driver was funded by NEC Australia P/L * and NEC Corporation @@ -35,6 +35,8 @@ * Jan 1999, Version 1.9 * Oct 1999, Version 1.10 * Nov 1999, Version 1.11 + * Jan 2000, Version 1.12 + * Feb 2000, Version 1.13 * * History: * 0.6b: first version in official kernel, Linux 1.3.46 @@ -54,7 +56,7 @@ * The new replacment for it is, but Linux doesn't yet support this. * Alan Cox Linux 2.1.55 * 1.3: Set up a valid data descriptor 0x40 for buggy BIOS's - * 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by + * 1.4: Upgraded to support APM 1.2. Integrated ThinkPad suspend patch by * Dean Gaudet <dgaudet@arctic.org>. * C. Scott Ananian <cananian@alumni.princeton.edu> Linux 2.1.87 * 1.5: Fix segment register reloading (in case of bad segments saved @@ -110,6 +112,26 @@ * (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. + * Make sure power off only happens on CPU 0 + * (Paul "Rusty" Russell <rusty@linuxcare.com>). + * Do error notification to user mode if BIOS calls fail. + * Move entrypoint offset fix to ...boot/setup.S + * where it belongs (Cosmos <gis88564@cis.nctu.edu.tw>). + * Remove smp-power-off. SMP users must now specify + * "apm=power-off" on the kernel command line. Suggested + * by Jim Avera <jima@hal.com>, modified by Alan Cox + * <alan@lxorguk.ukuu.org.uk>. + * Register the /proc/apm entry even on SMP so that + * scripts that check for it before doing power off + * work (Jim Avera <jima@hal.com>). + * 1.13: Changes for new pm_ interfaces (Andy Henroid + * <andy_henroid@yahoo.com>). + * Modularize the code. * * APM 1.1 Reference: * @@ -145,13 +167,12 @@ #include <linux/apm_bios.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/pm.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/desc.h> -#include <linux/pm.h> - extern unsigned long get_cmos_time(void); extern void machine_real_restart(unsigned char *, int); @@ -172,10 +193,10 @@ extern int (*console_blank_hook)(int); * See Documentation/Config.help for the configuration options. * * Various options can be changed at boot time as follows: + * (We allow underscores for compatibility with the modules code) * apm=on/off enable/disable APM * [no-]debug log some debugging messages - * [no-]power-off power off on shutdown - * [no-]smp-power-off allow power off even for SMP + * [no-]power[-_]off power off on shutdown */ /* KNOWN PROBLEM MACHINES: @@ -219,8 +240,8 @@ extern int (*console_blank_hook)(int); /* * 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> + * This patched by Chad Miller <cmiller@surfsouth.com>, original code by + * David Chen <chen@ctpa04.mit.edu> */ #undef INIT_TIMER_AFTER_SUSPEND @@ -237,7 +258,7 @@ extern int (*console_blank_hook)(int); /* * If CONFIG_APM_IGNORE_SUSPEND_BOUNCE is defined then - * ignore suspend events for this amount of time + * ignore suspend events for this amount of time after a resume */ #define BOUNCE_INTERVAL (3 * HZ) @@ -248,14 +269,40 @@ extern int (*console_blank_hook)(int); __asm__ __volatile__("movl %%" #seg ",%0" : "=m" (where)) /* + * Maximum number of events stored + */ +#define APM_MAX_EVENTS 20 + +/* + * The per-file APM data + */ +struct apm_user { + int magic; + struct apm_user * next; + int suser: 1; + int suspend_wait: 1; + int suspend_result; + int suspends_pending; + int standbys_pending; + int suspends_read; + int standbys_read; + int event_head; + int event_tail; + apm_event_t events[APM_MAX_EVENTS]; +}; + +/* + * The magic number in apm_user + */ +#define APM_BIOS_MAGIC 0x4101 + +/* * Local variables */ static struct { unsigned long offset; unsigned short segment; } apm_bios_entry; -static int apm_enabled = 0; -static int smp_hack = 0; #ifdef CONFIG_APM_CPU_IDLE static int clock_slowed = 0; #endif @@ -274,12 +321,19 @@ static int got_clock_diff = 0; #endif static int debug = 0; static int apm_disabled = 0; -static int power_off_enabled = 1; +#ifdef CONFIG_SMP +static int power_off = 0; +#else +static int power_off = 1; +#endif +static int exit_kapmd = 0; +static int kapmd_running = 0; static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue); -static struct apm_bios_struct * user_list = NULL; +static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); +static struct apm_user * 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", @@ -380,6 +434,10 @@ static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in, __save_flags(flags); APM_DO_CLI; APM_DO_SAVE_SEGS; + /* + * N.B. We do NOT need a cld after the BIOS call + * because we always save and restore the flags. + */ __asm__ __volatile__(APM_DO_ZERO_SEGS "pushl %%edi\n\t" "pushl %%ebp\n\t" @@ -413,10 +471,14 @@ static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax) { int cx, dx, si; + /* + * N.B. We do NOT need a cld after the BIOS call + * because we always save and restore the flags. + */ __asm__ __volatile__(APM_DO_ZERO_SEGS "pushl %%edi\n\t" "pushl %%ebp\n\t" - "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry)"\n\t" + "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" "setc %%bl\n\t" "popl %%ebp\n\t" "popl %%edi\n\t" @@ -499,6 +561,7 @@ static void apm_do_busy(void) } } +#if 0 extern int hlt_counter; /* @@ -543,48 +606,63 @@ static void apm_cpu_idle(void) } } #endif +#endif + +#ifdef CONFIG_SMP +static int apm_magic(void * unused) +{ + while (1) + schedule(); +} +#endif static void apm_power_off(void) { +#ifdef CONFIG_APM_REAL_MODE_POWER_OFF + unsigned char po_bios_call[] = { + 0xb8, 0x00, 0x10, /* movw $0x1000,ax */ + 0x8e, 0xd0, /* movw ax,ss */ + 0xbc, 0x00, 0xf0, /* movw $0xf000,sp */ + 0xb8, 0x07, 0x53, /* movw $0x5307,ax */ + 0xbb, 0x01, 0x00, /* movw $0x0001,bx */ + 0xb9, 0x03, 0x00, /* movw $0x0003,cx */ + 0xcd, 0x15 /* int $0x15 */ + }; +#endif + /* - * smp_hack == 2 means that we would have enabled APM support - * except there is more than one processor and so most of - * the APM stuff is unsafe. We will still try power down - * because is is useful to some people and they know what - * they are doing because they booted with the smp-power-off - * kernel option. + * This may be called on an SMP machine. */ - if (apm_enabled || (smp_hack == 2)) { +#ifdef CONFIG_SMP + /* Some 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 #ifdef CONFIG_APM_REAL_MODE_POWER_OFF - unsigned char po_bios_call[] = { - 0xb8, 0x00, 0x10, /* movw $0x1000,ax */ - 0x8e, 0xd0, /* movw ax,ss */ - 0xbc, 0x00, 0xf0, /* movw $0xf000,sp */ - 0xb8, 0x07, 0x53, /* movw $0x5307,ax */ - 0xbb, 0x01, 0x00, /* movw $0x0001,bx */ - 0xb9, 0x03, 0x00, /* movw $0x0003,cx */ - 0xcd, 0x15 /* int $0x15 */ - }; - - machine_real_restart(po_bios_call, sizeof(po_bios_call)); + machine_real_restart(po_bios_call, sizeof(po_bios_call)); #else - (void) apm_set_power_state(APM_STATE_OFF); + (void) apm_set_power_state(APM_STATE_OFF); #endif - } } -#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) { @@ -632,12 +710,21 @@ static int apm_get_battery_status(u_short which, u_short *status, } #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; } @@ -655,7 +742,6 @@ static void apm_error(char *str, int err) } #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) -/* Called from console driver -- must make sure apm_enabled. */ static int apm_console_blank(int blank) { int error; @@ -664,9 +750,13 @@ static int apm_console_blank(int blank) state = blank ? APM_STATE_STANDBY : APM_STATE_READY; /* Blank the first display device */ error = set_power_state(0x100, state); - if (error != APM_SUCCESS) + if ((error != APM_SUCCESS) && (error != APM_NO_ERROR)) { /* try to blank them all instead */ error = set_power_state(0x1ff, state); + if ((error != APM_SUCCESS) && (error != APM_NO_ERROR)) + /* try to blank device one instead */ + error = set_power_state(0x101, state); + } if ((error == APM_SUCCESS) || (error == APM_NO_ERROR)) return 1; apm_error("set display", error); @@ -674,20 +764,20 @@ static int apm_console_blank(int blank) } #endif -static int queue_empty(struct apm_bios_struct * as) +static int queue_empty(struct apm_user *as) { return as->event_head == as->event_tail; } -static apm_event_t get_queued_event(struct apm_bios_struct * as) +static apm_event_t get_queued_event(struct apm_user *as) { as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS; return as->events[as->event_tail]; } -static void queue_event(apm_event_t event, struct apm_bios_struct *sender) +static void queue_event(apm_event_t event, struct apm_user *sender) { - struct apm_bios_struct * as; + struct apm_user * as; if (user_list == NULL) return; @@ -751,18 +841,11 @@ static void get_time_diff(void) #endif } -static void suspend(void) +static void reinit_timer(void) { - int err; #ifdef INIT_TIMER_AFTER_SUSPEND unsigned long flags; -#endif - get_time_diff(); - err = apm_set_power_state(APM_STATE_SUSPEND); - if ((err != APM_SUCCESS) && (err != APM_NO_ERROR)) - apm_error("suspend", err); -#ifdef INIT_TIMER_AFTER_SUSPEND save_flags(flags); cli(); /* set the clock to 100 Hz */ @@ -774,7 +857,27 @@ static void suspend(void) udelay(10); restore_flags(flags); #endif +} + +static int suspend(void) +{ + int err; + int ret; + struct apm_user *as; + + get_time_diff(); + err = apm_set_power_state(APM_STATE_SUSPEND); + reinit_timer(); set_time(); + ret = (err == APM_SUCCESS) || (err == APM_NO_ERROR); + if (!ret) + apm_error("suspend", err); + for (as = user_list; as != NULL; as = as->next) { + as->suspend_wait = 0; + as->suspend_result = (ret ? 0 : -EIO); + } + wake_up_interruptible(&apm_suspend_waitqueue); + return ret; } static void standby(void) @@ -806,15 +909,14 @@ static apm_event_t get_event(void) return 0; } -static int send_event(apm_event_t event, apm_event_t undo, - struct apm_bios_struct *sender) +static int send_event(apm_event_t event, struct apm_user *sender) { switch (event) { case APM_SYS_SUSPEND: case APM_CRITICAL_SUSPEND: case APM_USER_SUSPEND: /* map all suspends to ACPI D3 */ - if (pm_send_request(PM_SUSPEND, (void*) 3)) { + if (pm_send_request(PM_SUSPEND, (void *)3)) { if (apm_bios_info.version > 0x100) apm_set_power_state(APM_STATE_REJECT); return 0; @@ -823,11 +925,7 @@ static int send_event(apm_event_t event, apm_event_t undo, case APM_NORMAL_RESUME: case APM_CRITICAL_RESUME: /* map all resumes to ACPI D0 */ - if (pm_send_request(PM_RESUME, 0)) { - if (apm_bios_info.version > 0x100) - apm_set_power_state(APM_STATE_REJECT); - return 0; - } + (void) pm_send_request(PM_RESUME, (void *)0); break; } @@ -864,7 +962,7 @@ static void check_events(void) if (waiting_for_resume) break; #endif - if (send_event(event, APM_STANDBY_RESUME, NULL)) { + if (send_event(event, NULL)) { #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND waiting_for_resume = 1; #endif @@ -888,12 +986,12 @@ static void check_events(void) if (waiting_for_resume) break; #endif - if (send_event(event, APM_NORMAL_RESUME, NULL)) { + if (send_event(event, NULL)) { #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND waiting_for_resume = 1; #endif if (suspends_pending <= 0) - suspend(); + (void) suspend(); } break; @@ -908,13 +1006,13 @@ static void check_events(void) ignore_bounce = 1; #endif set_time(); - send_event(event, 0, NULL); + send_event(event, NULL); break; case APM_CAPABILITY_CHANGE: case APM_LOW_BATTERY: case APM_POWER_STATUS_CHANGE: - send_event(event, 0, NULL); + send_event(event, NULL); break; case APM_UPDATE_TIME: @@ -922,7 +1020,7 @@ static void check_events(void) break; case APM_CRITICAL_SUSPEND: - suspend(); + (void) suspend(); break; } } @@ -930,18 +1028,18 @@ static void check_events(void) static void apm_event_handler(void) { - static int pending_count = 0; + static int pending_count = 4; int err; if ((standbys_pending > 0) || (suspends_pending > 0)) { - if ((apm_bios_info.version > 0x100) && (pending_count-- <= 0)) { + if ((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); } } else - pending_count = 0; + pending_count = 4; check_events(); } @@ -957,13 +1055,17 @@ static void apm_event_handler(void) static void apm_mainloop(void) { DECLARE_WAITQUEUE(wait, current); - apm_enabled = 1; + + if (smp_num_cpus > 1) + return; add_wait_queue(&apm_waitqueue, &wait); current->state = TASK_INTERRUPTIBLE; for (;;) { /* Nothing to do, just sleep for the timeout */ schedule_timeout(APM_CHECK_TIMEOUT); + if (exit_kapmd) + break; /* * Ok, check all events, check for idle (and mark us sleeping @@ -976,11 +1078,11 @@ static void apm_mainloop(void) continue; if (apm_do_idle()) { unsigned long start = jiffies; - do { + while (system_idle()) { apm_do_idle(); if (jiffies - start > APM_CHECK_TIMEOUT) break; - } while (system_idle()); + } apm_do_busy(); apm_event_handler(); } @@ -988,7 +1090,7 @@ static void apm_mainloop(void) } } -static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func) +static int check_apm_user(struct apm_user *as, const char *func) { if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) { printk(KERN_ERR "apm: %s passed bad filp", func); @@ -999,13 +1101,13 @@ static int check_apm_bios_struct(struct apm_bios_struct *as, const char *func) static ssize_t do_read(struct file *fp, char *buf, size_t count, loff_t *ppos) { - struct apm_bios_struct * as; + struct apm_user * as; int i; apm_event_t event; DECLARE_WAITQUEUE(wait, current); as = fp->private_data; - if (check_apm_bios_struct(as, "read")) + if (check_apm_user(as, "read")) return -EIO; if (count < sizeof(apm_event_t)) return -EINVAL; @@ -1053,10 +1155,10 @@ repeat: static unsigned int do_poll(struct file *fp, poll_table * wait) { - struct apm_bios_struct * as; + struct apm_user * as; as = fp->private_data; - if (check_apm_bios_struct(as, "select")) + if (check_apm_user(as, "poll")) return 0; poll_wait(fp, &apm_waitqueue, wait); if (!queue_empty(as)) @@ -1067,11 +1169,11 @@ static unsigned int do_poll(struct file *fp, poll_table * wait) static int do_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) { - struct apm_bios_struct * as; - int send_ok = 1; + struct apm_user * as; + DECLARE_WAITQUEUE(wait, current); as = filp->private_data; - if (check_apm_bios_struct(as, "ioctl")) + if (check_apm_user(as, "ioctl")) return -EIO; if (!as->suser) return -EPERM; @@ -1081,11 +1183,9 @@ static int do_ioctl(struct inode * inode, struct file *filp, as->standbys_read--; as->standbys_pending--; standbys_pending--; - } - else - send_ok = send_event(APM_USER_STANDBY, - APM_STANDBY_RESUME, as); - if (send_ok && (standbys_pending <= 0)) + } else if (!send_event(APM_USER_STANDBY, as)) + return -EAGAIN; + if (standbys_pending <= 0) standby(); break; case APM_IOC_SUSPEND: @@ -1093,12 +1193,25 @@ static int do_ioctl(struct inode * inode, struct file *filp, as->suspends_read--; as->suspends_pending--; suspends_pending--; + } else if (!send_event(APM_USER_SUSPEND, as)) + return -EAGAIN; + if (suspends_pending <= 0) { + if (!suspend()) + return -EIO; + } else { + as->suspend_wait = 1; + add_wait_queue(&apm_suspend_waitqueue, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + if ((as->suspend_wait == 0) + || signal_pending(current)) + break; + schedule(); + } + current->state = TASK_RUNNING; + remove_wait_queue(&apm_suspend_waitqueue, &wait); + return as->suspend_result; } - else - send_ok = send_event(APM_USER_SUSPEND, - APM_NORMAL_RESUME, as); - if (send_ok && (suspends_pending <= 0)) - suspend(); break; default: return -EINVAL; @@ -1108,10 +1221,10 @@ static int do_ioctl(struct inode * inode, struct file *filp, static int do_release(struct inode * inode, struct file * filp) { - struct apm_bios_struct * as; + struct apm_user * as; as = filp->private_data; - if (check_apm_bios_struct(as, "release")) + if (check_apm_user(as, "release")) return 0; filp->private_data = NULL; if (as->standbys_pending > 0) { @@ -1122,12 +1235,12 @@ static int do_release(struct inode * inode, struct file * filp) if (as->suspends_pending > 0) { suspends_pending -= as->suspends_pending; if (suspends_pending <= 0) - suspend(); + (void) suspend(); } if (user_list == as) user_list = as->next; else { - struct apm_bios_struct * as1; + struct apm_user * as1; for (as1 = user_list; (as1 != NULL) && (as1->next != as); @@ -1139,17 +1252,21 @@ static int do_release(struct inode * inode, struct file * filp) as1->next = as->next; } kfree_s(as, sizeof(*as)); + MOD_DEC_USE_COUNT; return 0; } static int do_open(struct inode * inode, struct file * filp) { - struct apm_bios_struct * as; + struct apm_user * as; - as = (struct apm_bios_struct *)kmalloc(sizeof(*as), GFP_KERNEL); + MOD_INC_USE_COUNT; + + as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL); if (as == NULL) { printk(KERN_ERR "apm: cannot allocate struct of size %d bytes", sizeof(*as)); + MOD_DEC_USE_COUNT; return -ENOMEM; } as->magic = APM_BIOS_MAGIC; @@ -1184,11 +1301,10 @@ static int apm_get_info(char *buf, char **start, off_t fpos, int length) int time_units = -1; char *units = "?"; - if (!apm_enabled) - return 0; p = buf; - if (!(error = apm_get_power_status(&bx, &cx, &dx))) { + if ((smp_num_cpus == 1) && + !(error = apm_get_power_status(&bx, &cx, &dx))) { ac_line_status = (bx >> 8) & 0xff; battery_status = bx & 0xff; if ((cx & 0xff) != 0xff) @@ -1264,6 +1380,11 @@ static int apm(void *unused) char * power_stat; char * bat_stat; + kapmd_running = 1; + + exit_files(current); /* daemonize doesn't do exit_files */ + daemonize(); + strcpy(current->comm, "kapmd"); sigfillset(¤t->blocked); @@ -1278,7 +1399,7 @@ static int apm(void *unused) apm_bios_info.version = 0x100; } } - if (debug) { + if (debug && (smp_num_cpus == 1)) { printk(KERN_INFO "apm: Connection version %d.%d\n", (apm_bios_info.version >> 8) & 0xff, apm_bios_info.version & 0xff); @@ -1328,32 +1449,51 @@ static int apm(void *unused) * 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.. */ - if (power_off_enabled) + if (power_off) pm_power_off = apm_power_off; #ifdef CONFIG_MAGIC_SYSRQ sysrq_power_off = apm_power_off; #endif #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) - console_blank_hook = apm_console_blank; + if (smp_num_cpus == 1) + console_blank_hook = apm_console_blank; #endif pm_active = 1; apm_mainloop(); + + pm_active = 0; + +#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) + if (smp_num_cpus == 1) + console_blank_hook = NULL; +#endif +#ifdef CONFIG_MAGIC_SYSRQ + sysrq_power_off = NULL; +#endif + if (power_off) + pm_power_off = NULL; + + kapmd_running = 0; + return 0; } @@ -1371,10 +1511,9 @@ static int __init apm_setup(char *str) str += 3; if (strncmp(str, "debug", 5) == 0) debug = !invert; - if (strncmp(str, "power-off", 9) == 0) - power_off_enabled = !invert; - if (strncmp(str, "smp-power-off", 13) == 0) - smp_hack = !invert; + if ((strncmp(str, "power-off", 9) == 0) || + (strncmp(str, "power_off", 9) == 0)) + power_off = !invert; str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); @@ -1394,7 +1533,7 @@ static struct file_operations apm_bios_fops = { static struct miscdevice apm_device = { APM_MINOR_DEV, - "apm", + "apm_bios", &apm_bios_fops }; @@ -1455,7 +1594,10 @@ static int __init apm_init(void) printk(KERN_NOTICE "apm: disabled on user request.\n"); APM_INIT_ERROR_RETURN; } - + if ((smp_num_cpus > 1) && !power_off) { + printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n"); + APM_INIT_ERROR_RETURN; + } if (PM_IS_ACTIVE()) { printk(KERN_NOTICE "apm: overridden by ACPI.\n"); APM_INIT_ERROR_RETURN; @@ -1472,9 +1614,6 @@ static int __init apm_init(void) _set_limit((char *)&gdt[APM_40 >> 3], 4095 - (0x40 << 4)); apm_bios_entry.offset = apm_bios_info.offset; -#ifdef CONFIG_APM_BAD_ENTRY_OFFSET - apm_bios_entry.offset &= 0xffff; -#endif apm_bios_entry.segment = APM_CS; set_base(gdt[APM_CS >> 3], __va((unsigned long)apm_bios_info.cseg << 4)); @@ -1483,18 +1622,16 @@ static int __init apm_init(void) set_base(gdt[APM_DS >> 3], __va((unsigned long)apm_bios_info.dseg << 4)); #ifndef APM_RELAX_SEGMENTS - if (apm_bios_info.version == 0x100) + if (apm_bios_info.version == 0x100) { #endif - { /* For ASUS motherboard, Award BIOS rev 110 (and others?) */ _set_limit((char *)&gdt[APM_CS >> 3], 64 * 1024 - 1); /* For some unknown machine. */ _set_limit((char *)&gdt[APM_CS_16 >> 3], 64 * 1024 - 1); /* For the DEC Hinote Ultra CT475 (and others?) */ _set_limit((char *)&gdt[APM_DS >> 3], 64 * 1024 - 1); - } #ifndef APM_RELAX_SEGMENTS - else { + } else { _set_limit((char *)&gdt[APM_CS >> 3], (apm_bios_info.cseg_len - 1) & 0xffff); _set_limit((char *)&gdt[APM_CS_16 >> 3], @@ -1504,21 +1641,36 @@ static int __init apm_init(void) } #endif -#ifdef CONFIG_SMP + create_proc_info_entry("apm", 0, NULL, apm_get_info); + + 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; + printk(KERN_NOTICE + "apm: disabled - APM is not SMP safe (power off active).\n"); 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; } -__initcall(apm_init); +static void __exit apm_exit(void) +{ + misc_deregister(&apm_device); + remove_proc_entry("apm", NULL); + exit_kapmd = 1; + while (kapmd_running) + schedule(); +} + +module_init(apm_init); +module_exit(apm_exit); + +MODULE_AUTHOR("Stephen Rothwell"); +MODULE_DESCRIPTION("Advanced Power Management"); +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, "Enable debug mode"); + +EXPORT_NO_SYMBOLS; |