[PATCH] APM fixes and updates

From: Stephen Rothwell (sfr@linuxcare.com)
Date: Tue Apr 04 2000 - 22:19:12 EST


Hi Linus,

This patch (against 2.3.99pre4-4) does the following:

        Allow user mode programs to reject standby and suspend operations.
        Make power off work on SMP again.
        Add APM_IOC_ENABLE.
        Use set_current_state.
        Make CONFIG_APM_CPU_IDLE boot time settable.
        Make CONFIG_APM_REAL_MODE_POWER_OFF boot time settable.
        Make CONFIG_APM_RTC_IS_GMT boot time settable.
        Remove CONFIG_APM_IGNORE_USER_SUSPEND as this can now be done by
                rejecting USER_SUSPEND events from user mode i.e. in apmd.
        Add APM_IOC_LAST_ERROR.

Could you please apply this as the first three items above are very
important to various people.

This patch is also available on the apm web page at
http://linuxcare.com.au/apm/

Cheers,
Stephen

-- 
Stephen Rothwell, Open Source Project Engineer, Linuxcare, Inc.
+61-2-62628990 tel, +61-2-62628991 fax 
sfr@linuxcare.com, http://www.linuxcare.com/ 
Linuxcare. Support for the revolution.

diff -ruN 2.3.99pre4-4/Documentation/Configure.help 2.3.99pre4-4-APM/Documentation/Configure.help --- 2.3.99pre4-4/Documentation/Configure.help Wed Apr 5 12:49:02 2000 +++ 2.3.99pre4-4-APM/Documentation/Configure.help Wed Apr 5 12:58:05 2000 @@ -12206,12 +12206,6 @@ and read Documentation/modules.txt. The module will be called apm.o. -Ignore USER SUSPEND -CONFIG_APM_IGNORE_USER_SUSPEND - This option will ignore USER SUSPEND requests. On machines with a - compliant APM BIOS, you want to say N. However, on the NEC Versa M - series notebooks, it is necessary to say Y because of a BIOS bug. - Enable APM at boot time CONFIG_APM_DO_ENABLE Enable APM features at boot time. From page 36 of the APM BIOS @@ -12238,6 +12232,10 @@ whenever the CPU becomes idle. (On machines with more than one CPU, this option does nothing.) + This just sets the default behaviour and may be modified using the + "apm=do_idle" kernel command line option or the do_idle module + option. + Enable console blanking using APM CONFIG_APM_DISPLAY_BLANK Enable console blanking using the APM. Some laptops can use this to @@ -12278,6 +12276,10 @@ reason not to use GMT in your RTC is if you also run a broken OS that doesn't understand GMT. + This just sets the default behaviour and may be modified using the + "apm=rtc_is_gmt" kernel command line option or the rtc_is_gmt module + option. + Allow interrupts during APM BIOS calls CONFIG_APM_ALLOW_INTS Normally we disable external interrupts while we are making calls to @@ -12292,6 +12294,10 @@ Use real mode APM BIOS calls to switch off the computer. This is a work-around for a number of buggy BIOSes. Switch this option on if your computer crashes instead of powering off properly. + + This just sets the default behaviour and may be modified using the + "apm=real_mode_power_off" kernel command line option or the + real_mode_power_off module option. Watchdog Timer Support CONFIG_WATCHDOG diff -ruN 2.3.99pre4-4/arch/i386/kernel/apm.c 2.3.99pre4-4-APM/arch/i386/kernel/apm.c --- 2.3.99pre4-4/arch/i386/kernel/apm.c Fri Mar 10 12:15:02 2000 +++ 2.3.99pre4-4-APM/arch/i386/kernel/apm.c Wed Apr 5 12:58:05 2000 @@ -132,6 +132,19 @@ * 1.13: Changes for new pm_ interfaces (Andy Henroid * <andy_henroid@yahoo.com>). * Modularize the code. + * 1.14: Allow user mode programs to reject standby and suspend + * operations (Craig Markwardt <craigm@lheamail.gsfc.nasa.gov>). + * Make power off work on SMP again (Tony Hoyle + * <tmh@magenta-logic.com> and <zlatko@iskon.hr>) modified by sfr. + * Use set_current_state (Richard Gooch <rgooch@ras.ucalgary.ca>). + * 1.15: Make CONFIG_APM_CPU_IDLE boot time settable. + * Make CONFIG_APM_REAL_MODE_POWER_OFF boot time settable. + * Make CONFIG_APM_RTC_IS_GMT boot time settable. + * Remove CONFIG_APM_IGNORE_USER_SUSPEND as this can now be + * done by rejecting USER_SUSPEND events from user mode i.e. + * in apmd. + * Add APM_IOC_ENABLE (Edward A. Falk <falconer@best.com>). + * Add APM_IOC_LAST_ERROR. * * APM 1.1 Reference: * @@ -197,6 +210,10 @@ * apm=on/off enable/disable APM * [no-]debug log some debugging messages * [no-]power[-_]off power off on shutdown + * [no-]do[-_]idle do BIOS idle calls + * [no-]real[-_]mode[-_]power[-_]off switch to real mode before + * powering off + * [no-]rtc[-_]is[-_]gmt assume the RTC stores GMT */ /* KNOWN PROBLEM MACHINES: @@ -286,6 +303,8 @@ int standbys_pending; int suspends_read; int standbys_read; + int rejects_pending; + int last_error; int event_head; int event_tail; apm_event_t events[APM_MAX_EVENTS]; @@ -303,37 +322,46 @@ unsigned long offset; unsigned short segment; } apm_bios_entry; +static int clock_slowed; #ifdef CONFIG_APM_CPU_IDLE -static int clock_slowed = 0; +static int do_idle = 1; +#else +static int do_idle; #endif -static int suspends_pending = 0; -static int standbys_pending = 0; +static int rejects_pending; +static int suspends_pending; +static int standbys_pending; #ifdef CONFIG_APM_IGNORE_MULTIPLE_SUSPEND -static int waiting_for_resume = 0; +static int waiting_for_resume; #endif #ifdef CONFIG_APM_RTC_IS_GMT -# define clock_cmos_diff 0 -# define got_clock_diff 1 +static int rtc_is_gmt = 1; #else -static long clock_cmos_diff; -static int got_clock_diff = 0; +static int rtc_is_gmt; #endif -static int debug = 0; -static int apm_disabled = 0; +static long clock_cmos_diff; +static int got_clock_diff; +static int debug; +static int apm_disabled; #ifdef CONFIG_SMP -static int power_off = 0; +static int power_off; #else static int power_off = 1; #endif -static int exit_kapmd = 0; -static int kapmd_running = 0; +static int exit_kapmd; +static int kapmd_running; +#ifdef CONFIG_APM_REAL_MODE_POWER_OFF +static int real_mode_power_off = 1; +#else +static int real_mode_power_off; +#endif static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue); static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); static struct apm_user * user_list = NULL; -static char driver_version[] = "1.13"; /* no spaces */ +static char driver_version[] = "1.15"; /* no spaces */ static char * apm_event_name[] = { "system standby", @@ -452,7 +480,11 @@ : "memory", "cc"); APM_DO_RESTORE_SEGS; __restore_flags(flags); - return *eax & 0xff; + if (*eax & 0xff) { + *eax = (*eax >> 8) & 0xff; + return 1; + } + return 0; } /* @@ -490,6 +522,8 @@ } APM_DO_RESTORE_SEGS; __restore_flags(flags); + if (error) + *eax = (*eax >> 8) & 0xff; return error; } @@ -498,7 +532,7 @@ u32 eax; if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax)) - return (eax >> 8) & 0xff; + return eax; *val = eax; return APM_SUCCESS; } @@ -512,7 +546,7 @@ if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy)) - return (eax >> 8) & 0xff; + return eax; *event = ebx; if (apm_bios_info.version < 0x0102) *info = ~0; /* indicate info not valid */ @@ -526,7 +560,7 @@ u32 eax; if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax)) - return (eax >> 8) & 0xff; + return eax; return APM_SUCCESS; } @@ -535,7 +569,6 @@ return set_power_state(APM_DEVICE_ALL, state); } -#ifdef CONFIG_APM_CPU_IDLE static int apm_do_idle(void) { u32 dummy; @@ -610,7 +643,6 @@ } } #endif -#endif #ifdef CONFIG_SMP static int apm_magic(void * unused) @@ -622,7 +654,6 @@ 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 */ @@ -632,7 +663,6 @@ 0xb9, 0x03, 0x00, /* movw $0x0003,cx */ 0xcd, 0x15 /* int $0x15 */ }; -#endif /* * This may be called on an SMP machine. @@ -645,11 +675,10 @@ schedule(); } #endif -#ifdef CONFIG_APM_REAL_MODE_POWER_OFF - machine_real_restart(po_bios_call, sizeof(po_bios_call)); -#else - (void) apm_set_power_state(APM_STATE_OFF); -#endif + if (real_mode_power_off) + machine_real_restart(po_bios_call, sizeof(po_bios_call)); + else + (void) apm_set_power_state(APM_STATE_OFF); } static int apm_enable_power_management(int enable) @@ -660,7 +689,7 @@ return APM_NOT_ENGAGED; if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL, enable, &eax)) - return (eax >> 8) & 0xff; + return eax; if (enable) apm_bios_info.flags &= ~APM_BIOS_DISABLED; else @@ -678,7 +707,7 @@ if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0, &eax, &ebx, &ecx, &edx, &dummy)) - return (eax >> 8) & 0xff; + return eax; *status = ebx; *bat = ecx; *life = edx; @@ -705,7 +734,7 @@ if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax, &ebx, &ecx, &edx, &esi)) - return (eax >> 8) & 0xff; + return eax; *status = ebx; *bat = ecx; *life = edx; @@ -722,7 +751,7 @@ && (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; + return eax; if (device == APM_DEVICE_ALL) { if (enable) apm_bios_info.flags &= ~APM_BIOS_DISENGAGED; @@ -779,7 +808,8 @@ return as->events[as->event_tail]; } -static void queue_event(apm_event_t event, struct apm_user *sender) +static void queue_event(apm_event_t event, struct apm_user *sender, + int reject_flag) { struct apm_user * as; @@ -811,9 +841,25 @@ as->standbys_pending++; standbys_pending++; break; + + case APM_NORMAL_RESUME: + if (reject_flag) { + as->suspend_wait = 0; + as->suspend_result = -EAGAIN; + as->last_error = APM_SUCCESS; + } + /* Fall through to */ + case APM_STANDBY_RESUME: + if (reject_flag) { + as->rejects_pending++; + rejects_pending++; + } + break; } } wake_up_interruptible(&apm_waitqueue); + if (reject_flag && (event == APM_NORMAL_RESUME)) + wake_up_interruptible(&apm_suspend_waitqueue); } static void set_time(void) @@ -830,19 +876,19 @@ static void get_time_diff(void) { -#ifndef CONFIG_APM_RTC_IS_GMT unsigned long flags; - /* - * Estimate time zone so that set_time can update the clock - */ - save_flags(flags); - clock_cmos_diff = -get_cmos_time(); - cli(); - clock_cmos_diff += CURRENT_TIME; + if (rtc_is_gmt) { + /* + * Estimate time zone so that set_time can update the clock + */ + clock_cmos_diff = -get_cmos_time(); + save_flags(flags); + cli(); + clock_cmos_diff += CURRENT_TIME; + restore_flags(flags); + } got_clock_diff = 1; - restore_flags(flags); -#endif } static void reinit_timer(void) @@ -866,32 +912,36 @@ 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) + if (err == APM_NO_ERROR) + err = APM_SUCCESS; + if (err != APM_SUCCESS) apm_error("suspend", err); for (as = user_list; as != NULL; as = as->next) { as->suspend_wait = 0; - as->suspend_result = (ret ? 0 : -EIO); + as->suspend_result = ((err == APM_SUCCESS) ? 0 : -EIO); + as->last_error = err; } wake_up_interruptible(&apm_suspend_waitqueue); - return ret; + return err; } -static void standby(void) +static int standby(void) { int err; get_time_diff(); err = apm_set_power_state(APM_STATE_STANDBY); - if ((err != APM_SUCCESS) && (err != APM_NO_ERROR)) + if (err == APM_NO_ERROR) + err = APM_SUCCESS; + if (err != APM_SUCCESS) apm_error("standby", err); + return err; } static apm_event_t get_event(void) @@ -933,10 +983,15 @@ break; } - queue_event(event, sender); + queue_event(event, sender, 0); return 1; } +static void unsend_event(apm_event_t event) +{ + (void) pm_send_all(PM_RESUME, (void *)0); +} + static void check_events(void) { apm_event_t event; @@ -971,16 +1026,11 @@ waiting_for_resume = 1; #endif if (standbys_pending <= 0) - standby(); + (void) standby(); } break; case APM_USER_SUSPEND: -#ifdef CONFIG_APM_IGNORE_USER_SUSPEND - if (apm_bios_info.version > 0x100) - apm_set_power_state(APM_STATE_REJECT); - break; -#endif case APM_SYS_SUSPEND: #ifdef CONFIG_APM_IGNORE_SUSPEND_BOUNCE if (ignore_bounce) @@ -1060,11 +1110,8 @@ { DECLARE_WAITQUEUE(wait, current); - if (smp_num_cpus > 1) - return; - add_wait_queue(&apm_waitqueue, &wait); - current->state = TASK_INTERRUPTIBLE; + set_current_state(TASK_INTERRUPTIBLE); for (;;) { /* Nothing to do, just sleep for the timeout */ schedule_timeout(APM_CHECK_TIMEOUT); @@ -1075,22 +1122,22 @@ * Ok, check all events, check for idle (and mark us sleeping * so as not to count towards the load average).. */ - current->state = TASK_INTERRUPTIBLE; + set_current_state(TASK_INTERRUPTIBLE); apm_event_handler(); -#ifdef CONFIG_APM_CPU_IDLE + if (!do_idle) + continue; if (!system_idle()) continue; if (apm_do_idle()) { unsigned long start = jiffies; while (system_idle()) { apm_do_idle(); - if (jiffies - start > APM_CHECK_TIMEOUT) + if ((jiffies - start) > APM_CHECK_TIMEOUT) break; } apm_do_busy(); apm_event_handler(); } -#endif } } @@ -1125,7 +1172,7 @@ schedule(); goto repeat; } - current->state = TASK_RUNNING; + set_current_state(TASK_RUNNING); remove_wait_queue(&apm_waitqueue, &wait); } i = count; @@ -1170,9 +1217,44 @@ return 0; } +static void apm_send_reject(struct apm_user *as, apm_event_t state) +{ + if (as->rejects_pending > 0) { + as->rejects_pending--; + rejects_pending--; + } else { + switch (state) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + queue_event(APM_NORMAL_RESUME, as, 1); + break; + case APM_SYS_STANDBY: + case APM_USER_STANDBY: + queue_event(APM_STANDBY_RESUME, as, 1); + break; + } + } + if ((rejects_pending <= 0) && + (suspends_pending <= 0) && + (standbys_pending <= 0)) { + switch (state) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + unsend_event(APM_NORMAL_RESUME); + break; + case APM_SYS_STANDBY: + case APM_USER_STANDBY: + unsend_event(APM_STANDBY_RESUME); + break; + } + apm_set_power_state(APM_STATE_REJECT); + } +} + static int do_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) { + int int_arg; struct apm_user * as; DECLARE_WAITQUEUE(wait, current); @@ -1190,7 +1272,7 @@ } else if (!send_event(APM_USER_STANDBY, as)) return -EAGAIN; if (standbys_pending <= 0) - standby(); + as->last_error = standby(); break; case APM_IOC_SUSPEND: if (as->suspends_read > 0) { @@ -1200,7 +1282,8 @@ } else if (!send_event(APM_USER_SUSPEND, as)) return -EAGAIN; if (suspends_pending <= 0) { - if (!suspend()) + as->last_error = suspend(); + if (as->last_error != APM_SUCCESS) return -EIO; } else { as->suspend_wait = 1; @@ -1212,11 +1295,34 @@ break; schedule(); } - current->state = TASK_RUNNING; + set_current_state(TASK_RUNNING); remove_wait_queue(&apm_suspend_waitqueue, &wait); return as->suspend_result; } break; + case APM_IOC_REJECT: + if (as->suspends_read > 0) { + as->suspends_read--; + as->suspends_pending--; + suspends_pending--; + apm_send_reject(as, APM_SYS_SUSPEND); + } else if (as->standbys_read > 0) { + as->standbys_read--; + as->standbys_pending--; + standbys_pending--; + apm_send_reject(as, APM_SYS_STANDBY); + } else + return -EINVAL; + break; + case APM_IOC_ENABLE: + get_user_ret(int_arg, (int *)arg, -EFAULT); + as->last_error = apm_enable_power_management(int_arg); + if (as->last_error != APM_SUCCESS) + return -EIO; + break; + case APM_IOC_LAST_ERROR: + put_user_ret(as->last_error, (int *)arg, -EFAULT); + break; default: return -EINVAL; } @@ -1233,12 +1339,15 @@ filp->private_data = NULL; if (as->standbys_pending > 0) { standbys_pending -= as->standbys_pending; - if (standbys_pending <= 0) - standby(); + rejects_pending -= as->rejects_pending; + as->rejects_pending = 0; + if ((standbys_pending <= 0) && (rejects_pending <=0)) + (void) standby(); } if (as->suspends_pending > 0) { suspends_pending -= as->suspends_pending; - if (suspends_pending <= 0) + rejects_pending -= as->rejects_pending; + if ((suspends_pending <= 0) && (rejects_pending <=0)) (void) suspend(); } if (user_list == as) @@ -1277,6 +1386,7 @@ as->event_tail = as->event_head = 0; as->suspends_pending = as->standbys_pending = 0; as->suspends_read = as->standbys_read = 0; + as->rejects_pending = 0; /* * XXX - this is a tiny bit broken, when we consider BSD * process accounting. If the device is opened by root, we @@ -1475,27 +1585,15 @@ #ifdef CONFIG_MAGIC_SYSRQ sysrq_power_off = apm_power_off; #endif + if (smp_num_cpus == 1) { #if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) - if (smp_num_cpus == 1) console_blank_hook = apm_console_blank; #endif - - pm_active = 1; - - apm_mainloop(); - - pm_active = 0; - + apm_mainloop(); #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; @@ -1518,6 +1616,15 @@ if ((strncmp(str, "power-off", 9) == 0) || (strncmp(str, "power_off", 9) == 0)) power_off = !invert; + if ((strncmp(str, "do-idle", 7) == 0) || + (strncmp(str, "do_idle", 7) == 0)) + do_idle = !invert; + if ((strncmp(str, "real-mode-power-off", 19) == 0) || + (strncmp(str, "real_mode_power_off", 19) == 0)) + real_mode_power_off = !invert; + if ((strncmp(str, "rtc-is-gmt", 10) == 0) || + (strncmp(str, "rtc_is_gmt", 10) == 0)) + rtc_is_gmt = !invert; str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); @@ -1606,6 +1713,7 @@ printk(KERN_NOTICE "apm: overridden by ACPI.\n"); APM_INIT_ERROR_RETURN; } + pm_active = 1; /* * Set up a segment that references the real mode segment 0x40 @@ -1664,9 +1772,18 @@ { misc_deregister(&apm_device); remove_proc_entry("apm", NULL); + +#ifdef CONFIG_MAGIC_SYSRQ + sysrq_power_off = NULL; +#endif + if (power_off) + pm_power_off = NULL; + exit_kapmd = 1; while (kapmd_running) schedule(); + + pm_active = 0; } module_init(apm_init); @@ -1676,5 +1793,13 @@ MODULE_DESCRIPTION("Advanced Power Management"); MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Enable debug mode"); +MODULE_PARM(power_off, "i"); +MODULE_PARM_DESC(power_off, "Enable power off"); +MODULE_PARM(do_idle, "i"); +MODULE_PARM_DESC(do_idle, "Enable calls to BIOS idle routine"); +MODULE_PARM(real_mode_power_off, "i"); +MODULE_PARM_DESC(real_mode_power_off, "Switch to real mode bfore powering off"); +MODULE_PARM(rtc_is_gmt, "i"); +MODULE_PARM_DESC(rtc_is_gmt, "The real time clock stores GMT"); EXPORT_NO_SYMBOLS; diff -ruN 2.3.99pre4-4/arch/i386/kernel/i386_ksyms.c 2.3.99pre4-4-APM/arch/i386/kernel/i386_ksyms.c --- 2.3.99pre4-4/arch/i386/kernel/i386_ksyms.c Thu Mar 16 06:08:04 2000 +++ 2.3.99pre4-4-APM/arch/i386/kernel/i386_ksyms.c Wed Apr 5 12:58:05 2000 @@ -26,6 +26,7 @@ extern void dump_thread(struct pt_regs *, struct user *); extern int dump_fpu(elf_fpregset_t *); extern spinlock_t rtc_lock; +extern void machine_real_restart(unsigned char *, int); #ifdef CONFIG_SMP extern void FASTCALL( __write_lock_failed(rwlock_t *rw)); @@ -58,6 +59,7 @@ EXPORT_SYMBOL(pm_power_off); EXPORT_SYMBOL(get_cmos_time); EXPORT_SYMBOL(apm_bios_info); +EXPORT_SYMBOL(machine_real_restart); EXPORT_SYMBOL(gdt); EXPORT_SYMBOL_NOVERS(__down_failed); diff -ruN 2.3.99pre4-4/include/linux/apm_bios.h 2.3.99pre4-4-APM/include/linux/apm_bios.h --- 2.3.99pre4-4/include/linux/apm_bios.h Mon Feb 21 15:37:09 2000 +++ 2.3.99pre4-4-APM/include/linux/apm_bios.h Wed Apr 5 12:58:05 2000 @@ -204,5 +204,8 @@ #define APM_IOC_STANDBY _IO('A', 1) #define APM_IOC_SUSPEND _IO('A', 2) +#define APM_IOC_REJECT _IO('A', 3) +#define APM_IOC_ENABLE _IOW('A', 4, int) +#define APM_IOC_LAST_ERROR _IOR('A', 5, int) #endif /* LINUX_APM_H */

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



This archive was generated by hypermail 2b29 : Fri Apr 07 2000 - 21:00:14 EST