?? apm.c
字號:
#define NR_APM_EVENT_NAME ARRAY_SIZE(apm_event_name)typedef struct lookup_t { int key; char * msg;} lookup_t;/* * The BIOS returns a set of standard error codes in AX when the * carry flag is set. */ static const lookup_t error_table[] = {/* N/A { APM_SUCCESS, "Operation succeeded" }, */ { APM_DISABLED, "Power management disabled" }, { APM_CONNECTED, "Real mode interface already connected" }, { APM_NOT_CONNECTED, "Interface not connected" }, { APM_16_CONNECTED, "16 bit interface already connected" },/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */ { APM_32_CONNECTED, "32 bit interface already connected" }, { APM_32_UNSUPPORTED, "32 bit interface not supported" }, { APM_BAD_DEVICE, "Unrecognized device ID" }, { APM_BAD_PARAM, "Parameter out of range" }, { APM_NOT_ENGAGED, "Interface not engaged" }, { APM_BAD_FUNCTION, "Function not supported" }, { APM_RESUME_DISABLED, "Resume timer disabled" }, { APM_BAD_STATE, "Unable to enter requested state" },/* N/A { APM_NO_EVENTS, "No events pending" }, */ { APM_NO_ERROR, "BIOS did not set a return code" }, { APM_NOT_PRESENT, "No APM present" }};#define ERROR_COUNT ARRAY_SIZE(error_table)/** * apm_error - display an APM error * @str: information string * @err: APM BIOS return code * * Write a meaningful log entry to the kernel log in the event of * an APM error. */ static void apm_error(char *str, int err){ int i; for (i = 0; i < ERROR_COUNT; i++) if (error_table[i].key == err) break; if (i < ERROR_COUNT) printk(KERN_NOTICE "apm: %s: %s\n", str, error_table[i].msg); else printk(KERN_NOTICE "apm: %s: unknown error code %#2.2x\n", str, err);}/* * Lock APM functionality to physical CPU 0 */ #ifdef CONFIG_SMPstatic cpumask_t apm_save_cpus(void){ cpumask_t x = current->cpus_allowed; /* Some bioses don't like being called from CPU != 0 */ set_cpus_allowed(current, cpumask_of_cpu(0)); BUG_ON(smp_processor_id() != 0); return x;}static inline void apm_restore_cpus(cpumask_t mask){ set_cpus_allowed(current, mask);}#else/* * No CPU lockdown needed on a uniprocessor */ #define apm_save_cpus() (current->cpus_allowed)#define apm_restore_cpus(x) (void)(x)#endif/* * These are the actual BIOS calls. Depending on APM_ZERO_SEGS and * apm_info.allow_ints, we are being really paranoid here! Not only * are interrupts disabled, but all the segment registers (except SS) * are saved and zeroed this means that if the BIOS tries to reference * any data without explicitly loading the segment registers, the kernel * will fault immediately rather than have some unforeseen circumstances * for the rest of the kernel. And it will be very obvious! :-) Doing * this depends on CS referring to the same physical memory as DS so that * DS can be zeroed before the call. Unfortunately, we can't do anything * about the stack segment/pointer. Also, we tell the compiler that * everything could change. * * Also, we KNOW that for the non error case of apm_bios_call, there * is no useful data returned in the low order 8 bits of eax. */#define APM_DO_CLI \ if (apm_info.allow_ints) \ local_irq_enable(); \ else \ local_irq_disable();#ifdef APM_ZERO_SEGS# define APM_DECL_SEGS \ unsigned int saved_fs; unsigned int saved_gs;# define APM_DO_SAVE_SEGS \ savesegment(fs, saved_fs); savesegment(gs, saved_gs)# define APM_DO_RESTORE_SEGS \ loadsegment(fs, saved_fs); loadsegment(gs, saved_gs)#else# define APM_DECL_SEGS# define APM_DO_SAVE_SEGS# define APM_DO_RESTORE_SEGS#endif/** * apm_bios_call - Make an APM BIOS 32bit call * @func: APM function to execute * @ebx_in: EBX register for call entry * @ecx_in: ECX register for call entry * @eax: EAX register return * @ebx: EBX register return * @ecx: ECX register return * @edx: EDX register return * @esi: ESI register return * * Make an APM call using the 32bit protected mode interface. The * caller is responsible for knowing if APM BIOS is configured and * enabled. This call can disable interrupts for a long period of * time on some laptops. The return value is in AH and the carry * flag is loaded into AL. If there is an error, then the error * code is returned in AH (bits 8-15 of eax) and this function * returns non-zero. */ static u8 apm_bios_call(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx, u32 *esi){ APM_DECL_SEGS unsigned long flags; cpumask_t cpus; int cpu; struct desc_struct save_desc_40; struct desc_struct *gdt; cpus = apm_save_cpus(); cpu = get_cpu(); gdt = get_cpu_gdt_table(cpu); save_desc_40 = gdt[0x40 / 8]; gdt[0x40 / 8] = bad_bios_desc; local_save_flags(flags); APM_DO_CLI; APM_DO_SAVE_SEGS; apm_bios_call_asm(func, ebx_in, ecx_in, eax, ebx, ecx, edx, esi); APM_DO_RESTORE_SEGS; local_irq_restore(flags); gdt[0x40 / 8] = save_desc_40; put_cpu(); apm_restore_cpus(cpus); return *eax & 0xff;}/** * apm_bios_call_simple - make a simple APM BIOS 32bit call * @func: APM function to invoke * @ebx_in: EBX register value for BIOS call * @ecx_in: ECX register value for BIOS call * @eax: EAX register on return from the BIOS call * * Make a BIOS call that does only returns one value, or just status. * If there is an error, then the error code is returned in AH * (bits 8-15 of eax) and this function returns non-zero. This is * used for simpler BIOS operations. This call may hold interrupts * off for a long time on some laptops. */static u8 apm_bios_call_simple(u32 func, u32 ebx_in, u32 ecx_in, u32 *eax){ u8 error; APM_DECL_SEGS unsigned long flags; cpumask_t cpus; int cpu; struct desc_struct save_desc_40; struct desc_struct *gdt; cpus = apm_save_cpus(); cpu = get_cpu(); gdt = get_cpu_gdt_table(cpu); save_desc_40 = gdt[0x40 / 8]; gdt[0x40 / 8] = bad_bios_desc; local_save_flags(flags); APM_DO_CLI; APM_DO_SAVE_SEGS; error = apm_bios_call_simple_asm(func, ebx_in, ecx_in, eax); APM_DO_RESTORE_SEGS; local_irq_restore(flags); gdt[0x40 / 8] = save_desc_40; put_cpu(); apm_restore_cpus(cpus); return error;}/** * apm_driver_version - APM driver version * @val: loaded with the APM version on return * * Retrieve the APM version supported by the BIOS. This is only * supported for APM 1.1 or higher. An error indicates APM 1.0 is * probably present. * * On entry val should point to a value indicating the APM driver * version with the high byte being the major and the low byte the * minor number both in BCD * * On return it will hold the BIOS revision supported in the * same format. */static int apm_driver_version(u_short *val){ u32 eax; if (apm_bios_call_simple(APM_FUNC_VERSION, 0, *val, &eax)) return (eax >> 8) & 0xff; *val = eax; return APM_SUCCESS;}/** * apm_get_event - get an APM event from the BIOS * @event: pointer to the event * @info: point to the event information * * The APM BIOS provides a polled information for event * reporting. The BIOS expects to be polled at least every second * when events are pending. When a message is found the caller should * poll until no more messages are present. However, this causes * problems on some laptops where a suspend event notification is * not cleared until it is acknowledged. * * Additional information is returned in the info pointer, providing * that APM 1.2 is in use. If no messges are pending the value 0x80 * is returned (No power management events pending). */ static int apm_get_event(apm_event_t *event, apm_eventinfo_t *info){ u32 eax; u32 ebx; u32 ecx; u32 dummy; if (apm_bios_call(APM_FUNC_GET_EVENT, 0, 0, &eax, &ebx, &ecx, &dummy, &dummy)) return (eax >> 8) & 0xff; *event = ebx; if (apm_info.connection_version < 0x0102) *info = ~0; /* indicate info not valid */ else *info = ecx; return APM_SUCCESS;}/** * set_power_state - set the power management state * @what: which items to transition * @state: state to transition to * * Request an APM change of state for one or more system devices. The * processor state must be transitioned last of all. what holds the * class of device in the upper byte and the device number (0xFF for * all) for the object to be transitioned. * * The state holds the state to transition to, which may in fact * be an acceptance of a BIOS requested state change. */ static int set_power_state(u_short what, u_short state){ u32 eax; if (apm_bios_call_simple(APM_FUNC_SET_STATE, what, state, &eax)) return (eax >> 8) & 0xff; return APM_SUCCESS;}/** * set_system_power_state - set system wide power state * @state: which state to enter * * Transition the entire system into a new APM power state. */ static int set_system_power_state(u_short state){ return set_power_state(APM_DEVICE_ALL, state);}/** * apm_do_idle - perform power saving * * This function notifies the BIOS that the processor is (in the view * of the OS) idle. It returns -1 in the event that the BIOS refuses * to handle the idle request. On a success the function returns 1 * if the BIOS did clock slowing or 0 otherwise. */ static int apm_do_idle(void){ u32 eax; u8 ret = 0; int idled = 0; int polling; polling = test_thread_flag(TIF_POLLING_NRFLAG); if (polling) { clear_thread_flag(TIF_POLLING_NRFLAG); smp_mb__after_clear_bit(); } if (!need_resched()) { idled = 1; ret = apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax); } if (polling) set_thread_flag(TIF_POLLING_NRFLAG); if (!idled) return 0; if (ret) { static unsigned long t; /* This always fails on some SMP boards running UP kernels. * Only report the failure the first 5 times. */ if (++t < 5) { printk(KERN_DEBUG "apm_do_idle failed (%d)\n", (eax >> 8) & 0xff); t = jiffies; } return -1; } clock_slowed = (apm_info.bios.flags & APM_IDLE_SLOWS_CLOCK) != 0; return clock_slowed;}/** * apm_do_busy - inform the BIOS the CPU is busy * * Request that the BIOS brings the CPU back to full performance. */ static void apm_do_busy(void){ u32 dummy; if (clock_slowed || ALWAYS_CALL_BUSY) { (void) apm_bios_call_simple(APM_FUNC_BUSY, 0, 0, &dummy); clock_slowed = 0; }}/* * If no process has really been interested in * the CPU for some time, we want to call BIOS * power management - we probably want * to conserve power. */#define IDLE_CALC_LIMIT (HZ * 100)#define IDLE_LEAKY_MAX 16static void (*original_pm_idle)(void);extern void default_idle(void);/** * apm_cpu_idle - cpu idling for APM capable Linux * * This is the idling function the kernel executes when APM is available. It * 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){ static int use_apm_idle; /* = 0 */ static unsigned int last_jiffies; /* = 0 */ static unsigned int last_stime; /* = 0 */ int apm_idle_done = 0; unsigned int jiffies_since_last_check = jiffies - last_jiffies; unsigned int bucket;recalc: if (jiffies_since_last_check > IDLE_CALC_LIMIT) { use_apm_idle = 0; last_jiffies = jiffies; last_stime = current->stime; } else if (jiffies_since_last_check > idle_period) { unsigned int idle_percentage; idle_percentage = current->stime - last_stime; idle_percentage *= 100; idle_percentage /= jiffies_since_last_check; use_apm_idle = (idle_percentage > idle_threshold); if (apm_info.forbid_idle) use_apm_idle = 0; last_jiffies = jiffies; last_stime = current->stime; } bucket = IDLE_LEAKY_MAX; while (!need_resched()) { if (use_apm_idle) { unsigned int t; t = jiffies; switch (apm_do_idle()) { case 0: apm_idle_done = 1; if (t != jiffies) { if (bucket) { bucket = IDLE_LEAKY_MAX; continue; } } else if (bucket) { bucket--; continue; } break; case 1: apm_idle_done = 1; break; default: /* BIOS refused */ break; } } if (original_pm_idle)
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -