summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/apm.c
blob: b3326fbef68051b95964154615672b2d4325011a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/* $Id: apm.c $ */
/** @file
 * APM BIOS support. Implements APM version 1.2.
 */

/*
 * Copyright (C) 2004-2023 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();
    }
}