apm.c revision 88c4b361fef237b4b991f435ade57577aa607c0c
/* $Id$ */
/** @file
* APM BIOS support. Implements APM version 1.2.
*/
/*
* Copyright (C) 2004-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <stdint.h>
#include <string.h>
#include "biosint.h"
#include "inlines.h"
#if DEBUG_APM
# define BX_DEBUG_APM(...) BX_DEBUG(__VA_ARGS__)
#else
# define BX_DEBUG_APM(...)
#endif
/* Implemented in assembly. */
extern void apm_pm16_entry(void);
#pragma aux apm_pm16_entry "*"
extern void apm_pm32_entry(void);
#pragma aux apm_pm32_entry "*"
/* APM function codes. */
enum apm_func {
APM_CHECK = 0x00, /* APM Installation Check */
APM_RM_CONN = 0x01, /* APM Real Mode Interface Connect */
APM_PM_CONN = 0x02, /* APM Protected Mode 16-bit Interface Connect */
APM_32_CONN = 0x03, /* APM Protected Mode 32-bit Interface Connect */
APM_DISCONN = 0x04, /* APM Interface Disconnect */
APM_IDLE = 0x05, /* CPU Idle */
APM_BUSY = 0x06, /* CPU Busy */
APM_SET_PWR = 0x07, /* Set Power State */
APM_ENBL_PM = 0x08, /* Enable/Disable Power Management */
APM_SET_DFL = 0x09, /* Restore APM BIOS Power-On Defaults */
APM_STATUS = 0x0A, /* Get Power Status */
APM_GET_EVT = 0x0B, /* Get PM Event */
APM_GET_PWR = 0x0C, /* Get Power State */
APM_DEVPM = 0x0D, /* Enable/Disable Device Power Management */
APM_DRV_VER = 0x0E, /* APM Driver Version */
APM_ENGAGE = 0x0F, /* Engage/Disengage Power Management */
APM_GET_CAP = 0x10 /* Get Capabilities */
};
enum apm_error {
APM_ERR_PM_DISABLED = 0x01, /* Power Management functionality disabled */
APM_ERR_RM_INUSE = 0x02, /* Real mode interface connection already established */
APM_ERR_NOT_CONN = 0x03, /* Interface not connected */
APM_ERR_PM_16_INUSE = 0x05, /* 16-bit protected mode interface connection already established */
APM_ERR_NO_PM_16 = 0x06, /* 16-bit protected mode interface not supported */
APM_ERR_PM_32_INUSE = 0x07, /* 32-bit protected mode interface connection already established */
APM_ERR_NO_PM_32 = 0x08, /* 32-bit protected mode interface not supported */
APM_ERR_BAD_DEV_ID = 0x09, /* Unrecognized device ID */
APM_ERR_INVAL_PARAM = 0x0A, /* Parameter out of range */
APM_ERR_NOT_ENGAGED = 0x0B, /* Interface not engaged */
APM_ERR_UNSUPPORTED = 0x0C, /* Function not supported */
APM_ERR_NO_RSM_TMR = 0x0D, /* Resume timer disabled */
APM_ERR_NO_EVENTS = 0x80 /* No power management events pending */
};
enum apm_power_state {
APM_PS_ENABLED = 0x00, /* APM enabled */
APM_PS_STANDBY = 0x01, /* Standby */
APM_PS_SUSPEND = 0x02, /* Suspend */
APM_PS_OFF = 0x03, /* Suspend */
};
#define APM_PORT 0x8900 /* Bochs power control port. */
// @todo: merge with system.c
#define AX r.gr.u.r16.ax
#define BX r.gr.u.r16.bx
#define CX r.gr.u.r16.cx
#define DX r.gr.u.r16.dx
#define SI r.gr.u.r16.si
#define DI r.gr.u.r16.di
#define BP r.gr.u.r16.bp
#define SP r.gr.u.r16.sp
#define FLAGS r.fl.u.r16.flags
#define EAX r.gr.u.r32.eax
#define EBX r.gr.u.r32.ebx
#define ECX r.gr.u.r32.ecx
#define EDX r.gr.u.r32.edx
#define ES r.es
#define APM_BIOS_SEG 0xF000 /* Real-mode APM segment. */
#define APM_BIOS_SEG_LEN 0xFFF0 /* Length of APM segment. */
/* The APM BIOS interface uses 32-bit registers *only* in the 32-bit
* protected mode connect call. Rather than saving/restoring 32-bit
* registers all the time, simply set the high words of those registers
* when necessary.
*/
void set_ebx_hi(uint16_t val);
#pragma aux set_ebx_hi = \
".386" \
"shl ebx, 16" \
parm [bx] modify exact [bx] nomemory;
void set_esi_hi(uint16_t val);
#pragma aux set_esi_hi = \
".386" \
"shl esi, 16" \
parm [si] modify exact [si] nomemory;
/* The APM handler has unique requirements. It must be callable from real and
* protected mode, both 16-bit and 32-bit. In protected mode, the caller must
* ensures that appropriate selectors are available; these only cover the BIOS
* code and data, hence the BIOS Data Area or EBDA cannot be accessed. CMOS is
* a good place to store information which needs to be accessible from several
* different contexts.
*
* Note that the 32-bit protected-mode handler only needs to thunk down to the
* 16-bit code. There's no need for separate 16-bit and 32-bit implementation.
*/
/* Output a null-terminated string to a specified port, without the
* terminating null character.
*/
static void apm_out_str_asm(uint16_t port, const char *s);
#pragma aux apm_out_str_asm = \
"mov al, [bx]" \
"next:" \
"out dx, al" \
"inc bx" \
"mov al, [bx]" \
"or al, al" \
"jnz next" \
parm [dx] [bx] modify exact [ax bx] nomemory;
/* Wrapper to avoid unnecessary inlining. */
void apm_out_str(const char *s, uint16_t port)
{
if (*s)
apm_out_str_asm(port, s);
}
void BIOSCALL apm_function(sys_regs_t r)
{
BX_DEBUG_APM("APM: AX=%04X BX=%04X CX=%04X\n", AX, BX, CX);
CLEAR_CF(); /* Boldly expect success. */
switch (GET_AL()) {
case APM_CHECK:
AX = 0x0102; /* Version 1.2 */
BX = 0x504D; /* 'PM' */
CX = 3; /* Bits 0/1: 16-bit/32-bit PM interface */
break;
case APM_RM_CONN:
// @todo: validate device ID
// @todo: validate current connection state
// @todo: change connection state
break;
case APM_PM_CONN:
// @todo: validate device ID
// @todo: validate current connection state
// @todo: change connection state
AX = APM_BIOS_SEG; /* 16-bit PM code segment (RM segment base). */
BX = (uint16_t)apm_pm16_entry; /* 16-bit PM entry point offset. */
CX = APM_BIOS_SEG; /* 16-bit data segment. */
SI = APM_BIOS_SEG_LEN; /* 16-bit PM code segment length. */
DI = APM_BIOS_SEG_LEN; /* Data segment length. */
break;
case APM_32_CONN:
// @todo: validate device ID
// @todo: validate current connection state
// @todo: change connection state
AX = APM_BIOS_SEG; /* 32-bit PM code segment (RM segment base). */
BX = (uint16_t)apm_pm32_entry; /* 32-bit entry point offset. */
CX = APM_BIOS_SEG; /* 16-bit code segment. */
DX = APM_BIOS_SEG; /* 16-bit data segment. */
SI = APM_BIOS_SEG_LEN; /* 32-bit code segment length. */
DI = APM_BIOS_SEG_LEN; /* Data segment length. */
set_ebx_hi(0);
set_esi_hi(APM_BIOS_SEG_LEN); /* 16-bit code segment length. */
break;
case APM_IDLE:
int_enable(); /* Simply halt the CPU with interrupts enabled. */
halt();
break;
case APM_SET_PWR:
// @todo: validate device ID
// @todo: validate current connection state
switch (CX) {
case APM_PS_STANDBY:
apm_out_str("Standby", APM_PORT);
break;
case APM_PS_SUSPEND:
apm_out_str("Suspend", APM_PORT);
break;
case APM_PS_OFF:
apm_out_str("Shutdown", APM_PORT); /* Should not return. */
break;
default:
SET_AH(APM_ERR_INVAL_PARAM);
SET_CF();
}
break;
case APM_DRV_VER:
AX = 0x0102; // @todo: Not right - must take driver version into account!
break;
case APM_DISCONN:
// @todo: actually perform a disconnect...
case APM_BUSY: /* Nothing to do as APM Idle doesn't slow CPU clock. */
break;
case APM_GET_EVT:
// @todo: error should be different if interface not connected + engaged
SET_AH(APM_ERR_NO_EVENTS); /* PM events don't happen. */
SET_CF();
break;
default:
BX_INFO("APM: Unsupported function AX=%04X BX=%04X called\n", AX, BX);
SET_AH(APM_ERR_UNSUPPORTED);
SET_CF();
}
}