📄 apm.c
字号:
/* * 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) \ __sti(); \ else \ __cli();#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_ZERO_SEGS \ "pushl %%ds\n\t" \ "pushl %%es\n\t" \ "xorl %%edx, %%edx\n\t" \ "mov %%dx, %%ds\n\t" \ "mov %%dx, %%es\n\t" \ "mov %%dx, %%fs\n\t" \ "mov %%dx, %%gs\n\t"# define APM_DO_POP_SEGS \ "popl %%es\n\t" \ "popl %%ds\n\t"# 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_ZERO_SEGS# define APM_DO_POP_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; unsigned long cpus = apm_save_cpus(); struct desc_struct save_desc_40; __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" "lcall %%cs:" SYMBOL_NAME_STR(apm_bios_entry) "\n\t" "setc %%al\n\t" "popl %%ebp\n\t" "popl %%edi\n\t" APM_DO_POP_SEGS : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx), "=S" (*esi) : "a" (func), "b" (ebx_in), "c" (ecx_in) : "memory", "cc"); APM_DO_RESTORE_SEGS; __restore_flags(flags); apm_restore_cpus(cpus); cpu_gdt_table[smp_processor_id()][0x40 / 8] = save_desc_40; 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; int cpu = smp_processor_id(); struct desc_struct save_desc_40; unsigned long cpus = apm_save_cpus(); save_desc_40 = cpu_gdt_table[cpu][0x40 / 8]; cpu_gdt_table[cpu][0x40 / 8] = bad_bios_desc; __save_flags(flags); APM_DO_CLI; APM_DO_SAVE_SEGS; { 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" "setc %%bl\n\t" "popl %%ebp\n\t" "popl %%edi\n\t" APM_DO_POP_SEGS : "=a" (*eax), "=b" (error), "=c" (cx), "=d" (dx), "=S" (si) : "a" (func), "b" (ebx_in), "c" (ecx_in) : "memory", "cc"); } APM_DO_RESTORE_SEGS; __restore_flags(flags); apm_restore_cpus(cpus); cpu_gdt_table[smp_processor_id()][0x40 / 8] = save_desc_40; 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 __init 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; if (apm_bios_call_simple(APM_FUNC_IDLE, 0, 0, &eax)) { 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); } 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->times.tms_stime; } else if (jiffies_since_last_check > idle_period) { unsigned int idle_percentage; idle_percentage = current->times.tms_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->times.tms_stime; } bucket = IDLE_LEAKY_MAX; while (!current->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) original_pm_idle(); else default_idle(); jiffies_since_last_check = jiffies - last_jiffies; if (jiffies_since_last_check > idle_period) goto recalc; } if (apm_idle_done) apm_do_busy();}/** * apm_power_off - ask the BIOS to power off * * Handle the power off sequence. This is the one piece of code we * will execute even on SMP machines. In order to deal with BIOS * bugs we support real mode APM BIOS power off calls. We also make * the SMP call on CPU0 as some systems will only honour this call * on their first cpu. */ static void apm_power_off(void){ 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 */ }; /* * This may be called on an SMP machine. */ if (apm_info.realmode_power_off) { (void)apm_save_cpus(); machine_real_restart(po_bios_call, sizeof(po_bios_call)); /* Never returns */ } else (void) set_system_power_state(APM_STATE_OFF);}/** * handle_poweroff - sysrq callback for power down * @key: key pressed (unused) * @pt_regs: register state (unused) * @kbd: keyboard state (unused) * @tty: tty involved (unused) * * When the user hits Sys-Rq o to power down the machine this is the * callback we use. */void handle_poweroff (int key, struct pt_regs *pt_regs, struct kbd_struct *kbd, struct tty_struct *tty) { apm_power_off();}struct sysrq_key_op sysrq_poweroff_op = { handler: handle_poweroff, help_msg: "Off", action_msg: "Power Off\n"};#ifdef CONFIG_APM_DO_ENABLE/** * apm_enable_power_management - enable BIOS APM power management * @enable: enable yes/no * * Enable or disable the APM BIOS power services. */ static int apm_enable_power_management(int enable){ u32 eax; if ((enable == 0) && (apm_info.bios.flags & APM_BIOS_DISENGAGED)) return APM_NOT_ENGAGED; if (apm_bios_call_simple(APM_FUNC_ENABLE_PM, APM_DEVICE_BALL, enable, &eax)) return (eax >> 8) & 0xff; if (enable) apm_info.bios.flags &= ~APM_BIOS_DISABLED; else apm_info.bios.flags |= APM_BIOS_DISABLED; return APM_SUCCESS;}#endif/** * apm_get_power_status - get current power state * @status: returned status * @bat: battery info * @life: estimated life * * Obtain the current power status from the APM BIOS. We return a * status which gives the rough battery status, and current power * source. The bat value returned give an estimate as a percentage * of life and a status value for the battery. The estimated life * if reported is a lifetime in secodnds/minutes at current powwer * consumption. */ static int apm_get_power_status(u_short *status, u_short *bat, u_short *life){ u32 eax; u32 ebx; u32 ecx; u32 edx; u32 dummy; if (apm_info.get_power_status_broken) return APM_32_UNSUPPORTED; if (apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0, &eax, &ebx, &ecx, &edx, &dummy)) return (eax >> 8) & 0xff; *status = ebx; *bat = ecx; if (apm_info.get_power_status_swabinminutes) { *life = swab16((u16)edx); *life |= 0x8000; } else *life = edx; return APM_SUCCESS;}#if 0static int apm_get_battery_status(u_short which, u_short *status, u_short *bat, u_short *life, u_short *nbat){ u32 eax; u32 ebx; u32 ecx; u32 edx; u32 esi; if (apm_info.connection_version < 0x0102) { /* pretend we only have one battery. */ if (which != 1) return APM_BAD_DEVICE; *nbat = 1; return apm_get_power_status(status, bat, life); } if (apm_bios_call(APM_FUNC_GET_STATUS, (0x8000 | (which)), 0, &eax, &ebx, &ecx, &edx, &esi))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -