summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/apm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/apm.c')
-rw-r--r--src/VBox/Devices/PC/BIOS/apm.c234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/BIOS/apm.c b/src/VBox/Devices/PC/BIOS/apm.c
new file mode 100644
index 00000000..8f392243
--- /dev/null
+++ b/src/VBox/Devices/PC/BIOS/apm.c
@@ -0,0 +1,234 @@
+/* $Id: apm.c $ */
+/** @file
+ * APM BIOS support. Implements APM version 1.2.
+ */
+
+/*
+ * Copyright (C) 2004-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "biosint.h"
+#include "inlines.h"
+#include "VBox/bios.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 "*"
+
+#if VBOX_BIOS_CPU >= 80386
+extern void apm_pm32_entry(void);
+#pragma aux apm_pm32_entry "*"
+#endif
+
+/* 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 */
+};
+
+/// @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
+ * ensure 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.
+ */
+
+/* Wrapper to avoid unnecessary inlining. */
+void apm_out_str(const char *s)
+{
+ if (*s)
+ out_ctrl_str_asm(VBOX_BIOS_SHUTDOWN_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;
+#if VBOX_BIOS_CPU >= 80386
+ 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;
+#endif
+ 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");
+ break;
+ case APM_PS_SUSPEND:
+ apm_out_str("Suspend");
+ break;
+ case APM_PS_OFF:
+ apm_out_str("Shutdown"); /* 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_STATUS:
+ /* We do not attempt to report battery status. */
+ BX = 0x01FF; /* AC line power, battery unknown. */
+ CX = 0x80FF; /* No battery. */
+ DX = 0xFFFF; /* No idea about remaining battery life. */
+ 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();
+ }
+}