--- /tmp/apm.c.orig Sat Dec 22 15:19:42 2001 +++ /tmp/apm.c Sat Dec 22 15:17:23 2001 @@ -341,6 +341,11 @@ #define APM_BIOS_MAGIC 0x4101 /* + * idle percentage above which bios idle calls are done + */ +#define BIOS_IDLE_TRIGGER 95 + +/* * Local variables */ static struct { @@ -349,6 +354,8 @@ } apm_bios_entry; #ifdef CONFIG_APM_CPU_IDLE static int clock_slowed; +static int idle_enabled = 1; +static int idle_threshold = BIOS_IDLE_TRIGGER; #endif static int suspends_pending; static int standbys_pending; @@ -735,63 +742,84 @@ } } -#if 0 -extern int hlt_counter; - /* - * If no process has been interested in this - * CPU for some time, we want to wake up the - * power management thread - we probably want + * If no process has really been interested in + * CPU for some time, we want to call BIOS + * power management - we probably want * to conserve power. */ #define HARD_IDLE_TIMEOUT (HZ/3) +#define IDLE_CALC_LIMIT (HZ*100) +#define IDLE_LEAKY_MAX 16 -/* This should wake up kapmd and ask it to slow the CPU */ -#define powermanagement_idle() do { } while (0) +static void (*sys_idle)(void); +static unsigned int last_jiffies = 0; +static unsigned int last_stime = 0; +static int use_apm_idle = 0; /** * apm_cpu_idle - cpu idling for APM capable Linux * * This is the idling function the kernel executes when APM is available. It - * tries to save processor time directly by using hlt instructions. A - * separate apm thread tries to do the BIOS power management. - * - * N.B. This is curently not used for kernels 2.4.x. + * tries to do BIOS powermanagement based on the average system idle time. + * Furthermore it calls the system default idle routine. */ static 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) + int apm_is_idle = 0; + unsigned int t1 = jiffies - last_jiffies; + unsigned int t2; + +recalc: if(t1 > IDLE_CALC_LIMIT) + goto reset; + + if(t1 > HARD_IDLE_TIMEOUT) { + t2 = current->times.tms_stime - last_stime; + t2 *= 100; + t2 /= t1; + if (t2 > idle_threshold) + use_apm_idle = 1; + else +reset: use_apm_idle = 0; + last_jiffies = jiffies; + last_stime = current->times.tms_stime; + } + + t2 = IDLE_LEAKY_MAX; + + while (!current->need_resched) { + if(use_apm_idle) { + t1 = jiffies; + switch (apm_do_idle()) { + case 0: apm_is_idle = 1; + if (t1 != jiffies) { + if (t2) { + t2 = IDLE_LEAKY_MAX; + continue; + } + } else if (t2) { + t2--; continue; - __cli(); - if (!current->need_resched) - safe_halt(); - else - __sti(); - continue; + } + break; + case 1: apm_is_idle = 1; + break; } - - /* - * Ok, do some power management - we've been idle for too long - */ - powermanagement_idle(); } - schedule(); - check_pgt_cache(); - start_idle = jiffies; + if (sys_idle) + sys_idle(); + + t1 = jiffies - last_jiffies; + if (t1 > HARD_IDLE_TIMEOUT) + goto recalc; } + + if (apm_is_idle) + apm_do_busy(); } #endif -#endif #ifdef CONFIG_SMP static int apm_magic(void * unused) @@ -1346,17 +1374,12 @@ static void apm_mainloop(void) { - int timeout = HZ; DECLARE_WAITQUEUE(wait, current); add_wait_queue(&apm_waitqueue, &wait); set_current_state(TASK_INTERRUPTIBLE); for (;;) { - /* Nothing to do, just sleep for the timeout */ - timeout = 2 * timeout; - if (timeout > APM_CHECK_TIMEOUT) - timeout = APM_CHECK_TIMEOUT; - schedule_timeout(timeout); + schedule_timeout(APM_CHECK_TIMEOUT); if (exit_kapmd) break; @@ -1366,34 +1389,6 @@ */ set_current_state(TASK_INTERRUPTIBLE); apm_event_handler(); -#ifdef CONFIG_APM_CPU_IDLE - if (!system_idle()) - continue; - - /* - * If we can idle... - */ - if (apm_do_idle() != -1) { - unsigned long start = jiffies; - while ((!exit_kapmd) && system_idle()) { - if (apm_do_idle()) { - set_current_state(TASK_INTERRUPTIBLE); - /* APM needs us to snooze .. either - the BIOS call failed (-1) or it - slowed the clock (1). We sleep - until it talks to us again */ - schedule_timeout(1); - } - if ((jiffies - start) > APM_CHECK_TIMEOUT) { - apm_event_handler(); - start = jiffies; - } - } - apm_do_busy(); - apm_event_handler(); - timeout = 1; - } -#endif } remove_wait_queue(&apm_waitqueue, &wait); } @@ -1814,6 +1809,17 @@ if ((strncmp(str, "realmode-power-off", 18) == 0) || (strncmp(str, "realmode_power_off", 18) == 0)) apm_info.realmode_power_off = !invert; +#ifdef CONFIG_APM_CPU_IDLE + if ((strncmp(str, "apm-idle", 8) == 0) || + (strncmp(str, "apm_idle", 8) == 0)) + idle_enabled = !invert; + if ((strncmp(str, "apm-idle-threshold=", 19) == 0) || + (strncmp(str, "apm_idle_threshold=", 19) == 0)) { + idle_threshold = simple_strtol(str + 19, NULL, 0); + if ( idle_threshold < 0 || idle_threshold > 99 ) + idle_threshold = BIOS_IDLE_TRIGGER; + } +#endif str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); @@ -1968,6 +1974,13 @@ misc_register(&apm_device); +#ifdef CONFIG_APM_CPU_IDLE + if (idle_enabled) { + sys_idle = pm_idle; + pm_idle = apm_cpu_idle; + } +#endif + return 0; } @@ -1975,6 +1988,11 @@ { int error; +#ifdef CONFIG_APM_CPU_IDLE + if (idle_enabled) + pm_idle = sys_idle; +#endif + if (((apm_info.bios.flags & APM_BIOS_DISENGAGED) == 0) && (apm_info.connection_version > 0x0100)) { error = apm_engage_power_management(APM_DEVICE_ALL, 0); @@ -2012,5 +2030,11 @@ MODULE_PARM(realmode_power_off, "i"); MODULE_PARM_DESC(realmode_power_off, "Switch to real mode before powering off"); +#ifdef CONFIG_APM_CPU_IDLE +MODULE_PARM(apm_idle, "i"); +MODULE_PARM_DESC(apm_idle, "Make BIOS idle calls when idle"); +MODULE_PARM(apm_idle_threshold, "i"); +MODULE_PARM_DESC(apm_idle_threshold, "System idle percentage for BIOS idle call s"); +#endif EXPORT_NO_SYMBOLS;