Goto sanos source index
//
// apm.c
//
// APM BIOS power management
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
//
// APM function codes
//
#define APM_FUNC_INST_CHECK 0x5300
#define APM_FUNC_REAL_CONN 0x5301
#define APM_FUNC_16BIT_CONN 0x5302
#define APM_FUNC_32BIT_CONN 0x5303
#define APM_FUNC_DISCONN 0x5304
#define APM_FUNC_IDLE 0x5305
#define APM_FUNC_BUSY 0x5306
#define APM_FUNC_SET_STATE 0x5307
#define APM_FUNC_ENABLE_PM 0x5308
#define APM_FUNC_RESTORE_BIOS 0x5309
#define APM_FUNC_GET_STATUS 0x530A
#define APM_FUNC_GET_EVENT 0x530B
#define APM_FUNC_GET_STATE 0x530C
#define APM_FUNC_ENABLE_DEV_PM 0x530D
#define APM_FUNC_VERSION 0x530E
#define APM_FUNC_ENGAGE_PM 0x530F
#define APM_FUNC_GET_CAP 0x5310
#define APM_FUNC_RESUME_TIMER 0x5311
#define APM_FUNC_RESUME_ON_RING 0x5312
#define APM_FUNC_TIMER 0x5313
//
// Power states
//
#define APM_STATE_READY 0x0000
#define APM_STATE_STANDBY 0x0001
#define APM_STATE_SUSPEND 0x0002
#define APM_STATE_OFF 0x0003
#define APM_STATE_BUSY 0x0004
#define APM_STATE_REJECT 0x0005
#define APM_STATE_OEM_SYS 0x0020
#define APM_STATE_OEM_DEV 0x0040
#define APM_STATE_DISABLE 0x0000
#define APM_STATE_ENABLE 0x0001
#define APM_STATE_DISENGAGE 0x0000
#define APM_STATE_ENGAGE 0x0001
//
// APM Device IDs
//
#define APM_DEVICE_BIOS 0x0000
#define APM_DEVICE_ALL 0x0001
#define APM_DEVICE_DISPLAY 0x0100
#define APM_DEVICE_STORAGE 0x0200
#define APM_DEVICE_PARALLEL 0x0300
#define APM_DEVICE_SERIAL 0x0400
#define APM_DEVICE_NETWORK 0x0500
#define APM_DEVICE_PCMCIA 0x0600
#define APM_DEVICE_BATTERY 0x8000
#define APM_DEVICE_OEM 0xE000
#define APM_DEVICE_OLD_ALL 0xFFFF
#define APM_DEVICE_CLASS 0x00FF
#define APM_DEVICE_MASK 0xFF00
//
// APM BIOS Installation Check Flags
//
#define APM_16_BIT_SUPPORT 0x0001
#define APM_32_BIT_SUPPORT 0x0002
#define APM_IDLE_SLOWS_CLOCK 0x0004
#define APM_BIOS_DISABLED 0x0008
#define APM_BIOS_DISENGAGED 0x0010
int apm_enabled = 0;
static unsigned long apm_conn_ver = 0;
static struct fullptr apm_entrypoint;
#pragma warning(disable: 4731) // C4731: frame pointer register 'ebp' modified by inline assembly code
static int apm_bios_call(unsigned long func, unsigned long ebx_in, unsigned long ecx_in,
unsigned long *eax_out, unsigned long *ebx_out, unsigned long *ecx_out,
unsigned long *edx_out, unsigned long *esi_out) {
__asm {
push edi
push esi
push ebx
push ecx
push ebp
push ds
push es
push fs
push gs
pushf
mov eax, [func]
mov ebx, [ebx_in]
mov ecx, [ecx_in]
CLI
call fword ptr [apm_entrypoint]
setc al
STI
popf
pop gs
pop fs
pop es
pop ds
pop ebp
mov edi, [eax_out]
mov [edi], eax
mov edi, [ebx_out]
mov [edi], ebx
mov edi, [ecx_out]
mov [edi], ecx
mov edi, [edx_out]
mov [edi], edx
mov edi, [esi_out]
mov [edi], esi
pop ecx
pop ebx
pop esi
pop edi
}
return *eax_out & 0xFF;
}
static int apm_bios_call_simple(unsigned long func, unsigned long ebx_in, unsigned long ecx_in,
unsigned long *eax_out) {
__asm {
push edi
push esi
push ebp
push ds
push es
push fs
push gs
pushf
mov eax, [func]
mov ebx, [ebx_in]
mov ecx, [ecx_in]
xor edx, edx
CLI
call fword ptr [apm_entrypoint]
setc al
STI
popf
pop gs
pop fs
pop es
pop ds
pop ebp
mov edi, [eax_out]
mov [edi], eax
pop esi
pop edi
}
return *eax_out & 0xFF;
}
int apm_driver_version(unsigned long *ver) {
unsigned long eax;
int rc;
rc = apm_bios_call_simple(APM_FUNC_VERSION, 0, *ver, &eax);
if (rc != 0) return (eax >> 8) & 0xFF;
*ver = (eax & 0xFFFF);
return 0;
}
int apm_set_power_state(unsigned long device, unsigned long state) {
unsigned long eax;
int rc;
rc = apm_bios_call_simple(APM_FUNC_SET_STATE, device, state, &eax);
if (rc != 0) return (eax >> 8) & 0xFF;
return 0;
}
int apm_set_system_power_state(unsigned long state) {
return apm_set_power_state(APM_DEVICE_ALL, state);
}
int apm_get_power_status(unsigned long *status, unsigned long *battery, unsigned long *life) {
unsigned long eax;
unsigned long ebx;
unsigned long ecx;
unsigned long edx;
unsigned long dummy;
int rc;
rc = apm_bios_call(APM_FUNC_GET_STATUS, APM_DEVICE_ALL, 0, &eax, &ebx, &ecx, &edx, &dummy);
if (rc != 0) return (eax >> 8) & 0xFF;
*status = ebx;
*battery = ecx;
*life = edx;
return 0;
}
int apm_enable_power_management(unsigned long enable) {
unsigned long eax;
int rc;
rc = apm_bios_call_simple(APM_FUNC_ENABLE_PM, apm_conn_ver > 0x0100 ? APM_DEVICE_ALL : APM_DEVICE_OLD_ALL, enable, &eax);
if (rc != 0) return (eax >> 8) & 0xFF;
return 0;
}
int apm_engage_power_management(unsigned long device, unsigned long enable) {
unsigned long eax;
int rc;
rc = apm_bios_call_simple(APM_FUNC_ENGAGE_PM, device, enable, &eax);
if (rc != 0) return (eax >> 8) & 0xFF;
return 0;
}
void apm_power_off() {
int rc;
if (apm_enabled) {
rc = apm_set_system_power_state(APM_STATE_OFF);
if (rc != 0) {
// If shutdown fails, try to engage power management and try again.
apm_engage_power_management(APM_DEVICE_ALL, 1);
rc = apm_set_system_power_state(APM_STATE_OFF);
}
if (rc != 0) {
kprintf(KERN_ERR "apm: error %d in apm_set_system_power_state()\n", rc);
}
}
}
int apm_proc(struct proc_file *pf, void *arg) {
int rc;
unsigned long status;
unsigned long battery;
unsigned long life;
char *power_stat;
char *bat_stat;
rc = apm_get_power_status(&status, &battery, &life);
if (rc != 0) {
pprintf(pf, "power status not available");
} else {
switch ((status >> 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";
}
switch (status & 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";
}
pprintf(pf, "AC %s, battery status %s, battery life ", power_stat, bat_stat);
if ((battery & 0xff) == 0xff) {
pprintf(pf, "unknown");
} else {
pprintf(pf, "%d%%", battery & 0xff);
}
if ((life & 0xffff) != 0xffff) pprintf(pf, " %d %s\n", life & 0x7fff, (life & 0x8000) ? "minutes" : "seconds");
pprintf(pf, "\n");
}
return 0;
}
int __declspec(dllexport) apm(struct unit *unit, char *opts) {
struct apmparams *apm = &syspage->bootparams.apm;
int rc;
unsigned long vaddr;
unsigned long cseg16len;
unsigned long cseg32len;
unsigned long dseglen;
int engage = get_num_option(opts, "engage", 2);
int enable = get_num_option(opts, "enable", 2);
// Skip if no APM BIOS detected in ldrinit
if (apm->version == 0) return 0;
cseg16len = apm->cseg16len;
cseg32len = apm->cseg32len;
dseglen = apm->dseglen;
switch (apm->version) {
case 0x0100:
cseg16len = 0x10000;
cseg32len = 0x10000;
dseglen = 0x10000;
break;
case 0x0101:
cseg16len = cseg32len;
break;
case 0x0102:
default:
if (cseg16len == 0) cseg16len = 0x10000;
if (cseg32len == 0) cseg32len = 0x10000;
if (dseglen == 0) dseglen = 0x10000;
}
//kprintf("apm: version 0x%x flags 0x%x entry %x\n", apm->version, apm->flags, apm->entry);
//kprintf("apm: cseg16 %x (%d bytes) cseg32 %x (%d bytes) dseg %x (%d bytes)\n", apm->cseg16, cseg16len, apm->cseg32, cseg32len, apm->dseg, dseglen);
// Setup APM selectors in GDT
vaddr = (unsigned long) iomap(apm->cseg32 << 4, cseg32len);
set_gdt_entry(GDT_APMCS, vaddr, cseg32len, D_CODE | D_DPL0 | D_READ | D_PRESENT, D_BIG);
vaddr = (unsigned long) iomap(apm->cseg16 << 4, cseg16len);
set_gdt_entry(GDT_APMCS16, vaddr, cseg16len, D_CODE | D_DPL0 | D_READ | D_PRESENT, 0);
vaddr = (unsigned long) iomap(apm->dseg << 4, dseglen);
set_gdt_entry(GDT_APMDS, vaddr, dseglen, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, D_BIG);
set_gdt_entry(GDT_APM40, (unsigned long) iomap(0x400, 4096), 4096 - 0x40 * 16, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, D_BIG);
// Setup APM entry point
apm_entrypoint.segment = SEL_APMCS;
apm_entrypoint.offset = apm->entry;
// Initialize APM
apm_conn_ver = apm->version;
if (apm_conn_ver > 0x0100) {
if (apm_conn_ver > 0x0102) apm_conn_ver = 0x0102;
rc = apm_driver_version(&apm_conn_ver);
if (rc != 0) {
//kprintf(KERN_DEBUG "apm: error %d in apm_driver_version()\n", rc);
apm_conn_ver = 0x0100;
}
}
//kprintf(KERN_DEBUG "apm: connection version %04x\n", apm_conn_ver);
if (enable == 1 || enable == 2 && (apm->flags & APM_BIOS_DISABLED) != 0) {
rc = apm_enable_power_management(1);
if (rc != 0) {
kprintf(KERN_ERR "apm: error %d in apm_enable_power_management()\n", rc);
return -EIO;
}
}
if (engage == 1 || engage == 2 && (apm->flags & APM_BIOS_DISENGAGED) != 0) {
rc = apm_engage_power_management(APM_DEVICE_ALL, 1);
if (rc != 0) {
kprintf(KERN_ERR "apm: error %d in apm_engage_power_management()\n", rc);
return -EIO;
}
}
apm_enabled = 1;
register_proc_inode("apm", apm_proc, NULL);
return 0;
}