📄 apm.c
字号:
static int do_open(struct inode * inode, struct file * filp){ struct apm_user * as; as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL); if (as == NULL) { printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n", sizeof(*as)); return -ENOMEM; } as->magic = APM_BIOS_MAGIC; as->event_tail = as->event_head = 0; as->suspends_pending = as->standbys_pending = 0; as->suspends_read = as->standbys_read = 0; /* * XXX - this is a tiny bit broken, when we consider BSD * process accounting. If the device is opened by root, we * instantly flag that we used superuser privs. Who knows, * we might close the device immediately without doing a * privileged operation -- cevans */ as->suser = capable(CAP_SYS_ADMIN); as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE; as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ; as->next = user_list; user_list = as; filp->private_data = as; return 0;}battery_hook_fp* apm_battery_hook;DECLARE_MUTEX(apm_battery_hook_sem);EXPORT_SYMBOL_GPL(apm_battery_hook);EXPORT_SYMBOL_GPL(apm_battery_hook_sem);static int apm_get_info(char *buf, char **start, off_t fpos, int length){ char * p; unsigned short bx; unsigned short cx; unsigned short dx; int error; unsigned short ac_line_status = 0xff; unsigned short battery_status = 0xff; unsigned short battery_flag = 0xff; int percentage = -1; int time_units = -1; char *units = "?"; p = buf; if ((smp_num_cpus == 1 || smp) && !(error = apm_get_power_status(&bx, &cx, &dx))) { ac_line_status = (bx >> 8) & 0xff; battery_status = bx & 0xff; if ((cx & 0xff) != 0xff) percentage = cx & 0xff; if (apm_info.connection_version > 0x100) { battery_flag = (cx >> 8) & 0xff; if (dx != 0xffff) { units = (dx & 0x8000) ? "min" : "sec"; time_units = dx & 0x7fff; } } } /* Some machines report no info via APM but it's still available; provide a hook for such drivers to fill stuff in */ down(&apm_battery_hook_sem); if (apm_battery_hook) apm_battery_hook(&battery_status, &battery_flag, &ac_line_status, &percentage, &time_units); up(&apm_battery_hook_sem); /* Arguments, with symbols from linux/apm_bios.h. Information is from the Get Power Status (0x0a) call unless otherwise noted. 0) Linux driver version (this will change if format changes) 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2. 2) APM flags from APM Installation Check (0x00): bit 0: APM_16_BIT_SUPPORT bit 1: APM_32_BIT_SUPPORT bit 2: APM_IDLE_SLOWS_CLOCK bit 3: APM_BIOS_DISABLED bit 4: APM_BIOS_DISENGAGED 3) AC line status 0x00: Off-line 0x01: On-line 0x02: On backup power (BIOS >= 1.1 only) 0xff: Unknown 4) Battery status 0x00: High 0x01: Low 0x02: Critical 0x03: Charging 0x04: Selected battery not present (BIOS >= 1.2 only) 0xff: Unknown 5) Battery flag bit 0: High bit 1: Low bit 2: Critical bit 3: Charging bit 7: No system battery 0xff: Unknown 6) Remaining battery life (percentage of charge): 0-100: valid -1: Unknown 7) Remaining battery life (time units): Number of remaining minutes or seconds -1: Unknown 8) min = minutes; sec = seconds */ p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n", driver_version, (apm_info.bios.version >> 8) & 0xff, apm_info.bios.version & 0xff, apm_info.bios.flags, ac_line_status, battery_status, battery_flag, percentage, time_units, units); return p - buf;}static int apm(void *unused){ unsigned short bx; unsigned short cx; unsigned short dx; int error; char * power_stat; char * bat_stat; kapmd_running = 1; daemonize(); strcpy(current->comm, "kapmd"); sigfillset(¤t->blocked);#ifdef CONFIG_SMP /* 2002/08/01 - WT * This is to avoid random crashes at boot time during initialization * on SMP systems in case of "apm=power-off" mode. Seen on ASUS A7M266D. * Some bioses don't like being called from CPU != 0. * Method suggested by Ingo Molnar. */ if (cpu_number_map(smp_processor_id()) != 0) { current->cpus_allowed = 1; schedule(); if (unlikely(cpu_number_map(smp_processor_id()) != 0)) BUG(); }#endif if (apm_info.connection_version == 0) { apm_info.connection_version = apm_info.bios.version; if (apm_info.connection_version > 0x100) { /* * We only support BIOSs up to version 1.2 */ if (apm_info.connection_version > 0x0102) apm_info.connection_version = 0x0102; error = apm_driver_version(&apm_info.connection_version); if (error != APM_SUCCESS) { apm_error("driver version", error); /* Fall back to an APM 1.0 connection. */ apm_info.connection_version = 0x100; } } } if (debug) printk(KERN_INFO "apm: Connection version %d.%d\n", (apm_info.connection_version >> 8) & 0xff, apm_info.connection_version & 0xff);#ifdef CONFIG_APM_DO_ENABLE if (apm_info.bios.flags & APM_BIOS_DISABLED) { /* * This call causes my NEC UltraLite Versa 33/C to hang if it * is booted with PM disabled but not in the docking station. * Unfortunate ... */ error = apm_enable_power_management(1); if (error) { apm_error("enable power management", error); return -1; } }#endif if ((apm_info.bios.flags & APM_BIOS_DISENGAGED) && (apm_info.connection_version > 0x0100)) { error = apm_engage_power_management(APM_DEVICE_ALL, 1); if (error) { apm_error("engage power management", error); return -1; } } if (debug && (smp_num_cpus == 1 || smp )) { error = apm_get_power_status(&bx, &cx, &dx); if (error) printk(KERN_INFO "apm: power status not available\n"); else { switch ((bx >> 8) & 0xff) { case 0: power_stat = "off line"; break; case 1: power_stat = "on line"; break; case 2: power_stat = "on backup power"; break; default: power_stat = "unknown"; break; } switch (bx & 0xff) { case 0: bat_stat = "high"; break; case 1: bat_stat = "low"; break; case 2: bat_stat = "critical"; break; case 3: bat_stat = "charging"; break; default: bat_stat = "unknown"; break; } printk(KERN_INFO "apm: AC %s, battery status %s, battery life ", power_stat, bat_stat); if ((cx & 0xff) == 0xff) printk("unknown\n"); else printk("%d%%\n", cx & 0xff); if (apm_info.connection_version > 0x100) { printk(KERN_INFO "apm: battery flag 0x%02x, battery life ", (cx >> 8) & 0xff); if (dx == 0xffff) printk("unknown\n"); else printk("%d %s\n", dx & 0x7fff, (dx & 0x8000) ? "minutes" : "seconds"); } } } /* Install our power off handler.. */ if (power_off) pm_power_off = apm_power_off; register_sysrq_key('o', &sysrq_poweroff_op); if (smp_num_cpus == 1 || smp) {#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) console_blank_hook = apm_console_blank;#endif apm_mainloop();#if defined(CONFIG_APM_DISPLAY_BLANK) && defined(CONFIG_VT) console_blank_hook = NULL;#endif } kapmd_running = 0; return 0;}#ifndef MODULEstatic int __init apm_setup(char *str){ int invert; while ((str != NULL) && (*str != '\0')) { if (strncmp(str, "off", 3) == 0) apm_disabled = 1; if (strncmp(str, "on", 2) == 0) apm_disabled = 0; if ((strncmp(str, "bounce-interval=", 16) == 0) || (strncmp(str, "bounce_interval=", 16) == 0)) bounce_interval = simple_strtol(str + 16, NULL, 0); if ((strncmp(str, "idle-threshold=", 15) == 0) || (strncmp(str, "idle_threshold=", 15) == 0)) idle_threshold = simple_strtol(str + 15, NULL, 0); if ((strncmp(str, "idle-period=", 12) == 0) || (strncmp(str, "idle_period=", 12) == 0)) idle_period = simple_strtol(str + 12, NULL, 0); invert = (strncmp(str, "no-", 3) == 0) || (strncmp(str, "no_", 3) == 0); if (invert) str += 3; if (strncmp(str, "debug", 5) == 0) debug = !invert; if (strncmp(str, "smp", 3) == 0) { smp = !invert; idle_threshold = 100; } if ((strncmp(str, "power-off", 9) == 0) || (strncmp(str, "power_off", 9) == 0)) power_off = !invert; if ((strncmp(str, "allow-ints", 10) == 0) || (strncmp(str, "allow_ints", 10) == 0)) apm_info.allow_ints = !invert; if ((strncmp(str, "broken-psr", 10) == 0) || (strncmp(str, "broken_psr", 10) == 0)) apm_info.get_power_status_broken = !invert; if ((strncmp(str, "realmode-power-off", 18) == 0) || (strncmp(str, "realmode_power_off", 18) == 0)) apm_info.realmode_power_off = !invert; str = strchr(str, ','); if (str != NULL) str += strspn(str, ", \t"); } return 1;}__setup("apm=", apm_setup);#endifstatic struct file_operations apm_bios_fops = { owner: THIS_MODULE, read: do_read, poll: do_poll, ioctl: do_ioctl, open: do_open, release: do_release,};static struct miscdevice apm_device = { APM_MINOR_DEV, "apm_bios", &apm_bios_fops};/* * Just start the APM thread. We do NOT want to do APM BIOS * calls from anything but the APM thread, if for no other reason * than the fact that we don't trust the APM BIOS. This way, * most common APM BIOS problems that lead to protection errors * etc will have at least some level of being contained... * * In short, if something bad happens, at least we have a choice * of just killing the apm thread.. */static int __init apm_init(void){ int i; struct proc_dir_entry *apm_proc; if (apm_info.bios.version == 0) { printk(KERN_INFO "apm: BIOS not found.\n"); return -ENODEV; } printk(KERN_INFO "apm: BIOS version %d.%d Flags 0x%02x (Driver version %s)\n", ((apm_info.bios.version >> 8) & 0xff), (apm_info.bios.version & 0xff), apm_info.bios.flags, driver_version); if ((apm_info.bios.flags & APM_32_BIT_SUPPORT) == 0) { printk(KERN_INFO "apm: no 32 bit BIOS support\n"); return -ENODEV; } if (allow_ints) apm_info.allow_ints = 1; if (broken_psr) apm_info.get_power_status_broken = 1; if (realmode_power_off) apm_info.realmode_power_off = 1; /* User can override, but default is to trust DMI */ if (apm_disabled != -1) apm_info.disabled = apm_disabled; /* * Fix for the Compaq Contura 3/25c which reports BIOS version 0.1 * but is reportedly a 1.0 BIOS. */ if (apm_info.bios.version == 0x001) apm_info.bios.version = 0x100; /* BIOS < 1.2 doesn't set cseg_16_len */ if (apm_info.bios.version < 0x102) apm_info.bios.cseg_16_len = 0; /* 64k */ if (debug) { printk(KERN_INFO "apm: entry %x:%lx cseg16 %x dseg %x", apm_info.bios.cseg, apm_info.bios.offset, apm_info.bios.cseg_16, apm_info.bios.dseg); if (apm_info.bios.version > 0x100) printk(" cseg len %x, dseg len %x", apm_info.bios.cseg_len, apm_info.bios.dseg_len); if (apm_info.bios.version > 0x101) printk(" cseg16 len %x", apm_info.bios.cseg_16_len); printk("\n"); } if (apm_info.disabled) { printk(KERN_NOTICE "apm: disabled on user request.\n"); return -ENODEV; } if ((smp_num_cpus > 1) && !power_off && !smp) { printk(KERN_NOTICE "apm: disabled - APM is not SMP safe.\n"); return -ENODEV; } if (PM_IS_ACTIVE()) { printk(KERN_NOTICE "apm: overridden by ACPI.\n"); return -ENODEV; } pm_active = 1; /* * Set up a segment that references the real mode segment 0x40 * that extends up to the end of page zero (that we have reserved). * This is for buggy BIOS's that refer to (real mode) segment 0x40 * even though they are called in protected mode. */ set_base(bad_bios_desc, __va((unsigned long)0x40 << 4)); _set_limit((char *)&bad_bios_desc, 4095 - (0x40 << 4)); apm_bios_entry.offset = apm_info.bios.offset; apm_bios_entry.segment = APM_CS; for (i = 0; i < NR_CPUS; i++) { set_base(cpu_gdt_table[i][APM_CS >> 3], __va((unsigned long)apm_info.bios.cseg << 4)); set_base(cpu_gdt_table[i][APM_CS_16 >> 3], __va((unsigned long)apm_info.bios.cseg_16 << 4)); set_base(cpu_gdt_table[i][APM_DS >> 3], __va((unsigned long)apm_info.bios.dseg << 4));#ifndef APM_RELAX_SEGMENTS if (apm_info.bios.version == 0x100) {#endif /* For ASUS motherboard, Award BIOS rev 110 (and others?) */ _set_limit((char *)&cpu_gdt_table[i][APM_CS >> 3], 64 * 1024 - 1); /* For some unknown machine. */ _set_limit((char *)&cpu_gdt_table[i][APM_CS_16 >> 3], 64 * 1024 - 1); /* For the DEC Hinote Ultra CT475 (and others?) */ _set_limit((char *)&cpu_gdt_table[i][APM_DS >> 3], 64 * 1024 - 1);#ifndef APM_RELAX_SEGMENTS } else { _set_limit((char *)&cpu_gdt_table[i][APM_CS >> 3], (apm_info.bios.cseg_len - 1) & 0xffff); _set_limit((char *)&cpu_gdt_table[i][APM_CS_16 >> 3], (apm_info.bios.cseg_16_len - 1) & 0xffff); _set_limit((char *)&cpu_gdt_table[i][APM_DS >> 3], (apm_info.bios.dseg_len - 1) & 0xffff); }#endif } apm_proc = create_proc_info_entry("apm", 0, NULL, apm_get_info); if (apm_proc) SET_MODULE_OWNER(apm_proc); kernel_thread(apm, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD); if (smp_num_cpus > 1 && !smp) { printk(KERN_NOTICE "apm: disabled - APM is not SMP safe (power off active).\n"); return 0; } misc_register(&apm_device); if (HZ != 100) idle_period = (idle_period * HZ) / 100; if (idle_threshold < 100) { original_pm_idle = pm_idle; pm_idle = apm_cpu_idle; set_pm_idle = 1; } return 0;}static void __exit apm_exit(void){ int error; if (set_pm_idle) pm_idle = original_pm_idle; if (((apm_info.bios.flags & APM_BIOS_DISENGAGED) == 0) && (apm_info.connection_version > 0x0100)) { error = apm_engage_power_management(APM_DEVICE_ALL, 0); if (error) apm_error("disengage power management", error); } misc_deregister(&apm_device); remove_proc_entry("apm", NULL); unregister_sysrq_key('o',&sysrq_poweroff_op); if (power_off) pm_power_off = NULL; exit_kapmd = 1; while (kapmd_running) schedule(); pm_active = 0;}module_init(apm_init);module_exit(apm_exit);MODULE_AUTHOR("Stephen Rothwell");MODULE_DESCRIPTION("Advanced Power Management");MODULE_LICENSE("GPL");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(bounce_interval, "i");MODULE_PARM_DESC(bounce_interval, "Set the number of ticks to ignore suspend bounces");MODULE_PARM(allow_ints, "i");MODULE_PARM_DESC(allow_ints, "Allow interrupts during BIOS calls");MODULE_PARM(broken_psr, "i");MODULE_PARM_DESC(broken_psr, "BIOS has a broken GetPowerStatus call");MODULE_PARM(realmode_power_off, "i");MODULE_PARM_DESC(realmode_power_off, "Switch to real mode before powering off");MODULE_PARM(idle_threshold, "i");MODULE_PARM_DESC(idle_threshold, "System idle percentage above which to make APM BIOS idle calls");MODULE_PARM(idle_period, "i");MODULE_PARM_DESC(idle_period, "Period (in sec/100) over which to caculate the idle percentage");MODULE_PARM(smp, "i");MODULE_PARM_DESC(smp, "Set this to enable APM use on an SMP platform. Use with caution on older systems");EXPORT_NO_SYMBOLS;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -