summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/invop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/invop.c')
-rw-r--r--src/VBox/Devices/PC/BIOS/invop.c360
1 files changed, 360 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/BIOS/invop.c b/src/VBox/Devices/PC/BIOS/invop.c
new file mode 100644
index 00000000..71955e9b
--- /dev/null
+++ b/src/VBox/Devices/PC/BIOS/invop.c
@@ -0,0 +1,360 @@
+/* $Id: invop.c $ */
+/** @file
+ * Real mode invalid opcode handler.
+ */
+
+/*
+ * Copyright (C) 2013-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"
+
+//#define EMU_386_LOADALL
+
+/* 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);
+
+#ifdef EMU_386_LOADALL
+
+/* The layout of 386 LOADALL descriptors. */
+typedef struct tag_ldal3_desc {
+ uint32_t attr; /* Segment attributes. */
+ uint32_t base; /* Expanded segment base. */
+ uint32_t limit; /* Expanded segment limit. */
+} ldal3_desc;
+
+/* The 386 LOADALL memory buffer pointed to by ES:EDI.
+ */
+typedef struct tag_ldall_386 {
+ uint32_t cr0; /* 00h */
+ uint32_t eflags; /* 04h */
+ uint32_t eip; /* 08h */
+ uint32_t edi; /* 0Ch */
+ uint32_t esi; /* 10h */
+ uint32_t ebp; /* 14h */
+ uint32_t esp; /* 18h */
+ uint32_t ebx; /* 1Ch */
+ uint32_t edx; /* 20h */
+ uint32_t ecx; /* 24h */
+ uint32_t eax; /* 28h */
+ uint32_t dr6; /* 2Ch */
+ uint32_t dr7; /* 30h */
+ uint32_t tr; /* 34h */
+ uint32_t ldt; /* 38h */
+ uint32_t gs; /* 3Ch */
+ uint32_t fs; /* 40h */
+ uint32_t ds; /* 44h */
+ uint32_t ss; /* 4Ch */
+ uint32_t cs; /* 48h */
+ uint32_t es; /* 50h */
+ ldal3_desc tss_desc; /* 54h */
+ ldal3_desc idt_desc; /* 60h */
+ ldal3_desc gdt_desc; /* 6Ch */
+ ldal3_desc ldt_desc; /* 78h */
+ ldal3_desc gs_desc; /* 84h */
+ ldal3_desc fs_desc; /* 90h */
+ ldal3_desc ds_desc; /* 9Ch */
+ ldal3_desc ss_desc; /* A8h */
+ ldal3_desc cs_desc; /* B4h */
+ ldal3_desc es_desc; /* C0h */
+} ldall_386_s;
+ct_assert(sizeof(ldall_386_s) == 0xCC);
+
+#endif
+
+/*
+ * 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;
+
+#ifdef EMU_386_LOADALL
+
+/* 386 version of the above. */
+void ldal3_finish(void);
+#pragma aux ldal3_finish = \
+ ".386" \
+ "mov sp, 28h" \
+ "popad" \
+ "mov sp, ss:[18h]" \
+ "sub sp, 6" \
+ "mov ss, ss:[48h]" \
+ "iret" \
+ parm nomemory modify nomemory aborts;
+
+/* 386 version of load_rm_segs.
+ * NB: Must not touch CX!
+ */
+void load_rm_seg3(int seg_flags, uint16_t ss_base);
+#pragma aux load_rm_seg3 = \
+ "mov ss, ax" \
+ "mov ax, ss:[44h]" \
+ "mov ds, ax" \
+ "mov ax, ss:[50h]" \
+ "mov es, ax" \
+ parm [ax] [cx] nomemory modify nomemory;
+
+#endif
+
+#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();
+#ifdef EMU_386_LOADALL
+ } else if (*(uint16_t __far *)ins == 0x070F) {
+ /* 386 LOADALL. NB: Same opcode as SYSRET. */
+ ldall_386_s __far *ldbuf = (void __far *)es :> gr.u.r16.di; /* Assume 16-bit value in EDI. */
+ ldall_286_s __far *ldbuf2 = 0 :> 0x800;
+ iret_addr_t __far *ret_addr;
+ uint32_t seg_base;
+ int seg_flags = 0;
+
+ /* NB: BIG FAT ASSUMPTION! Users of 386 LOADALL are assumed to also
+ * have a 286 LOADALL buffer at physical address 800h. We use unused fields
+ * in that buffer for temporary storage.
+ */
+
+ /* Set up return stack. */
+ ret_addr = ldbuf->ss :> (ldbuf->esp - sizeof(iret_addr_t));
+ ret_addr->ip = ldbuf->eip;
+ ret_addr->cs = ldbuf->cs;
+ ret_addr->flags.u.r16.flags = ldbuf->eflags;
+
+ /* Examine ES/DS. */
+ seg_base = ldbuf->es_desc.base;
+ if (seg_base != (uint32_t)ldbuf->es << 4)
+ seg_flags |= LOAD_ES;
+ seg_base = ldbuf->ds_desc.base;
+ 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). */
+ ldbuf2->unused2[0] = ldbuf->es_desc.limit;
+ ldbuf2->unused2[1] = (uint16_t)ldbuf->es_desc.base;
+ ldbuf2->unused2[2] = (ldbuf->es_desc.attr & 0xFF00) | (ldbuf->es_desc.base >> 16);
+ ldbuf2->unused2[3] = 0;
+
+ /* Store the DS base/limit/attributes in other unused words. */
+ ldbuf2->unused1[0] = ldbuf->ds_desc.limit;
+ ldbuf2->unused1[1] = (uint16_t)ldbuf->ds_desc.base;
+ ldbuf2->unused1[2] = (ldbuf->ds_desc.attr & 0xFF00) | (ldbuf->ds_desc.base >> 16);
+
+ /* Load the IDTR as specified. */
+ seg_base = ldbuf->idt_desc.base;
+ load_idtr(seg_base, ldbuf->idt_desc.limit);
+
+ /* Do the tricky bits now. */
+ load_rm_seg3(es, seg_flags);
+ load_pm_segs();
+ ldal3_finish();
+#endif
+ } else {
+ /* There isn't much point in executing the invalid opcode handler
+ * in an endless loop, so halt right here.
+ */
+ int_enable();
+ halt_forever();
+ }
+}