summaryrefslogtreecommitdiff
path: root/src/VBox/Devices/PC/BIOS/invop.c
blob: bdebf54eea8c9d3601a00f7bd0b160382dc6363a (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
/* $Id: invop.c $ */
/** @file
 * Real mode invalid opcode handler.
 */

/*
 * Copyright (C) 2013 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"

/* The layout of 286 LOADALL descriptors. */
typedef struct tag_ldall_desc {
    uint16_t    base_lo;        /* Bits 0-15 of segment base. */
    uint8_t     base_hi;        /* Bits 16-13 of segment base. */
    uint8_t     attr;           /* Segment attributes. */
    uint16_t    limit;          /* Segment limit. */
} ldall_desc;

/* The 286 LOADALL memory buffer at physical address 800h. From
 * The Undocumented PC.
 */
typedef struct tag_ldall_286 {
    uint16_t    unused1[3];
    uint16_t    msw;            /* 806h */
    uint16_t    unused2[7];
    uint16_t    tr;             /* 816h */
    uint16_t    flags;          /* 818h */
    uint16_t    ip;             /* 81Ah */
    uint16_t    ldt;            /* 81Ch */
    uint16_t    ds;             /* 81Eh */
    uint16_t    ss;             /* 820h */
    uint16_t    cs;             /* 822h */
    uint16_t    es;             /* 824h */
    uint16_t    di;             /* 826h */
    uint16_t    si;             /* 828h */
    uint16_t    bp;             /* 82Ah */
    uint16_t    sp;             /* 82Ch */
    uint16_t    bx;             /* 82Eh */
    uint16_t    dx;             /* 830h */
    uint16_t    cx;             /* 832h */
    uint16_t    ax;             /* 834h */
    ldall_desc  es_desc;        /* 836h */
    ldall_desc  cs_desc;        /* 83Ch */
    ldall_desc  ss_desc;        /* 842h */
    ldall_desc  ds_desc;        /* 848h */
    ldall_desc  gdt_desc;       /* 84Eh */
    ldall_desc  ldt_desc;       /* 854h */
    ldall_desc  idt_desc;       /* 85Ah */
    ldall_desc  tss_desc;       /* 860h */
} ldall_286_s;
ct_assert(sizeof(ldall_286_s) == 0x66);

/*
 * LOADALL emulation assumptions:
 *  - MSW indicates real mode
 *  - Standard real mode CS and SS is to be used
 *  - Segment values of non-RM segments (if any) do not matter
 *  - Standard segment attributes are used
 */

/* A wrapper for LIDT. */
void load_idtr(uint32_t base, uint16_t limit);
#pragma aux load_idtr =     \
    ".286p"                 \
    "mov bx, sp"            \
    "lidt fword ptr ss:[bx]"\
    parm caller reverse [] modify [bx] exact;

/* A wrapper for LGDT. */
void load_gdtr(uint32_t base, uint16_t limit);
#pragma aux load_gdtr =     \
    ".286p"                 \
    "mov bx, sp"            \
    "lgdt fword ptr ss:[bx]"\
    parm caller reverse [] modify [bx] exact;

/* Load DS/ES as real-mode segments. May be overwritten later.
 * NB: Loads SS with 80h to address the LOADALL buffer. Must
 * not touch CX!
 */
void load_rm_segs(int seg_flags);
#pragma aux load_rm_segs =  \
    "mov ax, 80h"           \
    "mov ss, ax"            \
    "mov ax, ss:[1Eh]"      \
    "mov ds, ax"            \
    "mov ax, ss:[24h]"      \
    "mov es, ax"            \
    parm [cx] nomemory modify nomemory;

/* Briefly switch to protected mode and load ES and/or DS if necessary.
 * NB: Trashes high bits of EAX, but that should be safe. Expects flags
 * in CX.
 */
void load_pm_segs(void);
#pragma aux load_pm_segs =  \
    ".386p"                 \
    "smsw ax"               \
    "inc  ax"               \
    "lmsw ax"               \
    "mov ax, 8"             \
    "test cx, 1"            \
    "jz  skip_es"           \
    "mov es, ax"            \
    "skip_es:"              \
    "test cx, 2"            \
    "jz  skip_ds"           \
    "mov bx,ss:[00h]"       \
    "mov ss:[08h], bx"      \
    "mov bx,ss:[02h]"       \
    "mov ss:[0Ah], bx"      \
    "mov bx,ss:[04h]"       \
    "mov ss:[0Ch], bx"      \
    "mov ds, ax"            \
    "skip_ds:"              \
    "mov eax, cr0"          \
    "dec ax"                \
    "mov cr0, eax"          \
    parm nomemory modify nomemory;

/* Complete LOADALL emulation: Restore general-purpose registers, stack
 * pointer, and CS:IP. NB: The LOADALL instruction stores registers in
 * the same order as PUSHA. Surprise, surprise!
 */
void ldall_finish(void);
#pragma aux ldall_finish =  \
    ".286"                  \
    "mov sp, 26h"           \
    "popa"                  \
    "mov sp, ss:[2Ch]"      \
    "sub sp, 6"             \
    "mov ss, ss:[20h]"      \
    "iret"                  \
    parm nomemory modify nomemory aborts;


#define LOAD_ES     0x01    /* ES needs to be loaded in protected mode. */
#define LOAD_DS     0x02    /* DS needs to be loaded in protected mode. */

/*
 * The invalid opcode handler exists to work around fishy application
 * code and paper over CPU generation differences:
 *
 * - Skip redundant LOCK prefixes (allowed on 8086, #UD on 286+).
 * - Emulate just enough of 286 LOADALL.
 *
 */
void BIOSCALL inv_op_handler(uint16_t ds, uint16_t es, pusha_regs_t gr, volatile iret_addr_t ra)
{
    void __far  *ins = ra.cs :> ra.ip;

    if (*(uint8_t __far *)ins == 0xF0) {
         /* LOCK prefix - skip over it and try again. */
        ++ra.ip;
    } else if (*(uint16_t __far *)ins == 0x050F) {
        /* 286 LOADALL. NB: Same opcode as SYSCALL. */
        ldall_286_s __far   *ldbuf = 0 :> 0x800;
        iret_addr_t __far   *ret_addr;
        uint32_t            seg_base;
        int                 seg_flags = 0;

        /* One of the challenges is that we must restore SS:SP as well
         * as CS:IP and FLAGS from the LOADALL buffer. We copy CS/IP/FLAGS
         * from the buffer just below the SS:SP values from the buffer so
         * that we can eventually IRET to the desired CS/IP/FLAGS/SS/SP
         * values in one go.
         */
        ret_addr = ldbuf->ss :> (ldbuf->sp - sizeof(iret_addr_t));
        ret_addr->ip = ldbuf->ip;
        ret_addr->cs = ldbuf->cs;
        ret_addr->flags.u.r16.flags = ldbuf->flags;

        /* Examine ES/DS. */
        seg_base = ldbuf->es_desc.base_lo | (uint32_t)ldbuf->es_desc.base_hi << 16;
        if (seg_base != (uint32_t)ldbuf->es << 4)
            seg_flags |= LOAD_ES;
        seg_base = ldbuf->ds_desc.base_lo | (uint32_t)ldbuf->ds_desc.base_hi << 16;
        if (seg_base != (uint32_t)ldbuf->ds << 4)
            seg_flags |= LOAD_DS;

        /* The LOADALL buffer doubles as a tiny GDT. */
        load_gdtr(0x800, 4 * 8 - 1);

        /* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
        ldbuf->unused2[0] = ldbuf->es_desc.limit;
        ldbuf->unused2[1] = ldbuf->es_desc.base_lo;
        ldbuf->unused2[2] = (ldbuf->es_desc.attr << 8) | ldbuf->es_desc.base_hi;
        ldbuf->unused2[3] = 0;

        /* Store the DS base/limit/attributes in other unused words. */
        ldbuf->unused1[0] = ldbuf->ds_desc.limit;
        ldbuf->unused1[1] = ldbuf->ds_desc.base_lo;
        ldbuf->unused1[2] = (ldbuf->ds_desc.attr << 8) | ldbuf->ds_desc.base_hi;

        /* Load the IDTR as specified. */
        seg_base = ldbuf->idt_desc.base_lo | (uint32_t)ldbuf->idt_desc.base_hi << 16;
        load_idtr(seg_base, ldbuf->idt_desc.limit);

        /* Do the tricky bits now. */
        load_rm_segs(seg_flags);
        load_pm_segs();
        ldall_finish();
    } else {
        /* There isn't much point in executing the invalid opcode handler
         * in an endless loop, so halt right here.
         */
        int_enable();
        halt_forever();
    }
}