* Copyright 2008 Jan Klötzke
* All rights reserved. Distributed under the terms of the MIT License.
*
* Emulation based on the Linux Real Mode Interface library.
* Copyright (C) 1998 by Josh Vanderhoof
*/
#include <vm.h>
#include <thread.h>
#include <vm86.h>
#include <arch_cpu.h>
#include <kernel.h>
#include <ksignal.h>
#include <string.h>
#include <stdlib.h>
#ifdef TRACE_VM86
# define TRACE(x...) dprintf("[vm86] " x)
# define TRACE_NP(x...) dprintf(x)
#else
# define TRACE(x...)
# define TRACE_NP(x...)
#endif
#define CLI 0xfa
#define INB 0xec
#define INBI 0xe4
#define INSB 0x6c
#define INSW 0x6d
#define INTn 0xcd
#define INW 0xed
#define INWI 0xe5
#define IRET 0xcf
#define OUTB 0xee
#define OUTBI 0xe6
#define OUTSB 0x6e
#define OUTSW 0x6f
#define OUTW 0xef
#define OUTWI 0xe7
#define POPF 0x9d
#define PUSHF 0x9c
#define STI 0xfb
#define I_FLAG (1u << 9)
#define DIRECTION_FLAG (1u << 10)
#define CSEG 0x2e
#define SSEG 0x36
#define DSEG 0x3e
#define ESEG 0x26
#define FSEG 0x64
#define GSEG 0x65
static inline uint16
get_int_seg(int i)
{
return *(uint16 *)(i * 4 + 2);
}
static inline uint16
get_int_off(int i)
{
return *(uint16 *)(i * 4);
}
static inline void
pushw(struct vm86_iframe *regs, uint16 i)
{
regs->esp -= 2;
*(uint16 *)(((uint32)regs->ss << 4) + regs->esp) = i;
}
static inline void
pushl(struct vm86_iframe *regs, uint32 i)
{
regs->esp -= 4;
*(uint32 *)(((uint32)regs->ss << 4) + regs->esp) = i;
}
static inline uint16
popw(struct vm86_iframe *regs)
{
uint16 ret = *(uint16 *)(((uint32)regs->ss << 4) + regs->esp);
regs->esp += 2;
return ret;
}
static inline uint32
popl(struct vm86_iframe *regs)
{
uint32 ret = *(uint32 *)(((uint32)regs->ss << 4) + regs->esp);
regs->esp += 4;
return ret;
}
static void
em_ins(struct vm86_iframe *regs, int size)
{
unsigned int edx, edi;
edx = regs->edx & 0xffff;
edi = regs->edi & 0xffff;
edi += (unsigned int)regs->es << 4;
if (regs->flags & DIRECTION_FLAG) {
if (size == 4)
asm volatile ("std; insl; cld" : "=D" (edi) : "d" (edx), "0" (edi));
else if (size == 2)
asm volatile ("std; insw; cld" : "=D" (edi) : "d" (edx), "0" (edi));
else
asm volatile ("std; insb; cld" : "=D" (edi) : "d" (edx), "0" (edi));
} else {
if (size == 4)
asm volatile ("cld; insl" : "=D" (edi) : "d" (edx), "0" (edi));
else if (size == 2)
asm volatile ("cld; insw" : "=D" (edi) : "d" (edx), "0" (edi));
else
asm volatile ("cld; insb" : "=D" (edi) : "d" (edx), "0" (edi));
}
edi -= (unsigned int)regs->es << 4;
regs->edi &= 0xffff0000;
regs->edi |= edi & 0xffff;
}
static void
em_rep_ins(struct vm86_iframe *regs, int size)
{
unsigned int cx;
cx = regs->ecx & 0xffff;
while (cx--)
em_ins(regs, size);
regs->ecx &= 0xffff0000;
}
static void
em_outs(struct vm86_iframe *regs, int size, int seg)
{
unsigned int edx, esi, base;
edx = regs->edx & 0xffff;
esi = regs->esi & 0xffff;
switch (seg) {
case CSEG: base = regs->cs; break;
case SSEG: base = regs->ss; break;
case ESEG: base = regs->es; break;
case FSEG: base = regs->fs; break;
case GSEG: base = regs->gs; break;
case DSEG:
default:
base = regs->ds;
break;
}
esi += base << 4;
if (regs->flags & DIRECTION_FLAG) {
if (size == 4) {
asm volatile ("std; outsl; cld"
: "=S" (esi) : "d" (edx), "0" (esi));
} else if (size == 2) {
asm volatile ("std; outsw; cld"
: "=S" (esi) : "d" (edx), "0" (esi));
} else {
asm volatile ("std; outsb; cld"
: "=S" (esi) : "d" (edx), "0" (esi));
}
} else {
if (size == 4)
asm volatile ("cld; outsl" : "=S" (esi) : "d" (edx), "0" (esi));
else if (size == 2)
asm volatile ("cld; outsw" : "=S" (esi) : "d" (edx), "0" (esi));
else
asm volatile ("cld; outsb" : "=S" (esi) : "d" (edx), "0" (esi));
}
esi -= base << 4;
regs->esi &= 0xffff0000;
regs->esi |= esi & 0xffff;
}
static void
em_rep_outs(struct vm86_iframe *regs, int size, int seg)
{
unsigned int cx;
cx = regs->ecx & 0xffff;
while (cx--)
em_outs(regs, size, seg);
regs->ecx &= 0xffff0000;
}
static inline void
em_inb(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("inb %w1, %b0"
: "=a" (regs->eax)
: "d" (port), "0" (regs->eax));
}
static inline void
em_inw(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("inw %w1, %w0"
: "=a" (regs->eax)
: "d" (port), "0" (regs->eax));
}
static inline void
em_inl(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("inl %w1, %0" : "=a" (regs->eax) : "d" (port));
}
static inline void
em_outb(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("outb %b0, %w1" : : "a" (regs->eax), "d" (port));
}
static inline void
em_outw(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("outw %w0, %w1" : : "a" (regs->eax), "d" (port));
}
static inline void
em_outl(struct vm86_iframe *regs, uint32 port)
{
asm volatile ("outl %0, %w1" : : "a" (regs->eax), "d" (port));
}
static int
emulate(struct vm86_state *state)
{
unsigned char *instruction;
struct {
unsigned char seg;
unsigned int size : 1;
unsigned int rep : 1;
} prefix = { DSEG, 0, 0 };
int ret = 0;
instruction = (unsigned char *)((unsigned int)state->regs.cs << 4);
instruction += state->regs.eip;
TRACE("emulate: ");
do {
switch (*instruction) {
case 0x66:
TRACE_NP("[SIZE] ");
prefix.size = 1 - prefix.size;
state->regs.eip++;
instruction++;
break;
case 0xf3:
TRACE_NP("REP ");
prefix.rep = 1;
state->regs.eip++;
instruction++;
break;
case CSEG:
case SSEG:
case DSEG:
case ESEG:
case FSEG:
case GSEG:
TRACE_NP("SEG(0x%02x) ", *instruction);
prefix.seg = *instruction;
state->regs.eip++;
instruction++;
break;
case 0xf0:
case 0xf2:
case 0x67:
TRACE_NP("IGN(0x%02x) ", *instruction);
state->regs.eip++;
instruction++;
break;
default:
ret = 1;
}
} while (!ret);
ret = 0;
switch (*instruction) {
case INSB:
TRACE_NP("INSB");
if (prefix.rep)
em_rep_ins(&state->regs, 1);
else
em_ins(&state->regs, 1);
state->regs.eip++;
break;
case INSW:
TRACE_NP("INSW");
if (prefix.rep) {
if (prefix.size)
em_rep_ins(&state->regs, 4);
else
em_rep_ins(&state->regs, 2);
} else {
if (prefix.size)
em_ins(&state->regs, 4);
else
em_ins(&state->regs, 2);
}
state->regs.eip++;
break;
case OUTSB:
TRACE_NP("OUTSB");
if (prefix.rep)
em_rep_outs(&state->regs, 1, prefix.seg);
else
em_outs(&state->regs, 1, prefix.seg);
state->regs.eip++;
break;
case OUTSW:
TRACE_NP("OUTSW");
if (prefix.rep) {
if (prefix.size)
em_rep_outs(&state->regs, 4, prefix.seg);
else
em_rep_outs(&state->regs, 2, prefix.seg);
} else {
if (prefix.size)
em_outs(&state->regs, 4, prefix.seg);
else
em_outs(&state->regs, 2, prefix.seg);
}
state->regs.eip++;
break;
case INBI:
em_inb(&state->regs, instruction[1]);
TRACE_NP("IN al(=0x%02lx), 0x%02x", state->regs.eax & 0xff,
instruction[1]);
state->regs.eip += 2;
break;
case INWI:
if (prefix.size) {
em_inl(&state->regs, instruction[1]);
TRACE_NP("IN eax(=0x%08lx), 0x%02x", state->regs.eax,
instruction[1]);
} else {
em_inw(&state->regs, instruction[1]);
TRACE_NP("IN ax(=0x%04lx), 0x%02x", state->regs.eax & 0xffff,
instruction[1]);
}
state->regs.eip += 2;
break;
case INB:
em_inb(&state->regs, state->regs.edx);
TRACE_NP("IN al(=0x%02lx), dx(0x%04lx)", state->regs.edx & 0xff,
state->regs.edx & 0xffff);
state->regs.eip++;
break;
case INW:
if (prefix.size) {
em_inl(&state->regs, state->regs.edx);
TRACE_NP("IN eax(=0x%08lx), dx(0x%04lx)", state->regs.edx,
state->regs.edx & 0xffff);
} else {
em_inw(&state->regs, state->regs.edx);
TRACE_NP("IN ax(=0x%04lx), dx(0x%04lx)",
state->regs.edx & 0xffff, state->regs.edx & 0xffff);
}
state->regs.eip++;
break;
case OUTBI:
em_outb(&state->regs, instruction[1]);
TRACE_NP("OUT 0x%02x, al(0x%02lx)", instruction[1],
state->regs.eax & 0xff);
state->regs.eip += 2;
break;
case OUTWI:
if (prefix.size) {
em_outl(&state->regs, instruction[1]);
TRACE_NP("OUT 0x%02x, eax(0x%08lx)", instruction[1],
state->regs.eax);
} else {
em_outw(&state->regs, instruction[1]);
TRACE_NP("OUT 0x%02x, ax(0x%04lx)", instruction[1],
state->regs.eax & 0xffff);
}
state->regs.eip += 2;
break;
case OUTB:
em_outb(&state->regs, state->regs.edx);
TRACE_NP("OUT dx(0x%04lx), al(0x%02lx)", state->regs.edx & 0xffff,
state->regs.eax & 0xff);
state->regs.eip++;
break;
case OUTW:
if (prefix.size) {
em_outl(&state->regs, state->regs.edx);
TRACE_NP("OUT dx(0x%04lx), eax(0x%08lx)",
state->regs.edx & 0xffff, state->regs.eax);
} else {
em_outw(&state->regs, state->regs.edx);
TRACE_NP("OUT dx(0x%04lx), ax(0x%04lx)",
state->regs.edx & 0xffff, state->regs.eax & 0xffff);
}
state->regs.eip++;
break;
case INTn:
TRACE_NP("INT 0x%02x", instruction[1]);
if (instruction[1] == RETURN_TO_32_INT) {
ret = 1;
} else {
uint16 flags = state->regs.flags;
flags &= ~I_FLAG;
flags |= (uint16)state->if_flag << 9;
pushw(&state->regs, flags);
pushw(&state->regs, state->regs.cs);
pushw(&state->regs, state->regs.eip + 2);
state->regs.cs = get_int_seg(instruction[1]);
state->regs.eip = get_int_off(instruction[1]);
state->if_flag = 0;
}
break;
case IRET:
TRACE_NP("IRET");
state->regs.eip = popw(&state->regs);
state->regs.cs = popw(&state->regs);
state->regs.flags = popw(&state->regs);
state->if_flag = (state->regs.flags >> 9) & 0x01;
break;
case CLI:
TRACE_NP("CLI");
state->if_flag = 0;
state->regs.eip++;
break;
case STI:
TRACE_NP("STI");
state->if_flag = 1;
state->regs.eip++;
break;
case PUSHF:
{
TRACE_NP("PUSHF");
uint32 flags = state->regs.flags;
flags &= ~I_FLAG;
flags |= (uint32)state->if_flag << 9;
if (prefix.size)
pushl(&state->regs, flags);
else
pushw(&state->regs, flags);
state->regs.eip++;
break;
}
case POPF:
{
TRACE_NP("POPF");
if (prefix.size)
state->regs.flags = popl(&state->regs);
else
state->regs.flags = popw(&state->regs);
state->if_flag = (state->regs.flags >> 9) & 0x01;
state->regs.eip++;
break;
}
default:
TRACE_NP("UNK");
ret = -1;
}
TRACE_NP("\n");
return ret;
}
static bool
vm86_fault_callback(addr_t address, addr_t faultAddress, bool isWrite)
{
struct iframe *frame = i386_get_user_iframe();
TRACE("Unhandled fault at 0x%08x touching 0x%08x while %s\n",
faultAddress, address, isWrite?"writing":"reading");
x86_vm86_return((struct vm86_iframe *)frame, B_BAD_ADDRESS);
return false;
}
the given \a state to default values and maps the BIOS into the teams
address space. The size of the available conventional RAM is given in
\a ramSize in bytes which should be greater or equal to VM86_MIN_RAM_SIZE.
*/
extern "C" status_t
vm86_prepare(struct vm86_state *state, unsigned int ramSize)
{
struct team *team = thread_get_current_thread()->team;
status_t ret = B_OK;
area_id vectors;
state->bios_area = B_ERROR;
if (ramSize < VM86_MIN_RAM_SIZE)
ramSize = VM86_MIN_RAM_SIZE;
void *address = (void *)0;
state->ram_area = create_area_etc(team->id, "dos", &address,
B_EXACT_ADDRESS, ramSize, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA, 0, 0);
if (state->ram_area < B_OK) {
ret = state->ram_area;
TRACE("Could not create RAM area\n");
goto error;
}
vectors = map_physical_memory("int vectors", (void *)0, 0x502,
B_ANY_KERNEL_BLOCK_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
&address);
if (vectors < B_OK) {
ret = vectors;
TRACE("Could not copy vectors\n");
goto error;
}
ret = user_memcpy((void *)0, address, 0x502);
*((uint32 *)0) = 0xdeadbeef;
delete_area(vectors);
if (ret != B_OK)
goto error;
address = (void *)0xa0000;
state->bios_area = vm_map_physical_memory(team->id, "bios",
&address, B_EXACT_ADDRESS, 0x60000, B_KERNEL_READ_AREA
| B_KERNEL_WRITE_AREA | B_READ_AREA | B_WRITE_AREA, (addr_t)0xa0000);
if (state->bios_area < B_OK) {
ret = state->bios_area;
TRACE("Could not map VGA BIOS.\n");
goto error;
}
return B_OK;
error:
if (state->bios_area > B_OK)
vm_delete_area(team->id, state->bios_area, true);
if (state->ram_area > B_OK)
vm_delete_area(team->id, state->ram_area, true);
return ret;
}
*/
extern "C" void
vm86_cleanup(struct vm86_state *state)
{
struct team *team = thread_get_current_thread()->team;
if (state->bios_area > B_OK)
vm_delete_area(team->id, state->bios_area, true);
if (state->ram_area > B_OK)
vm_delete_area(team->id, state->ram_area, true);
}
initialized by vm86_prepare() before, any registers needed by the BIOS too.
The function will return B_OK if the BIOS was called successfully,
otherwise an apropriate error code. After the call the registers are
copied back to \a state to reflect the status after the BIOS returned.
Any buffer which is given to the BIOS function may be allocated starting
from address 0x1000 up to the allocated RAM size (see vm86_prepare()). The
area below 0x1000 is not available because it is used for the interrupt
vector table, BIOS data area and as real mode stack.
*/
extern "C" status_t
vm86_do_int(struct vm86_state *state, uint8 vec)
{
int8 *ip;
int emuState = 0;
struct thread *thread = thread_get_current_thread();
status_t ret;
state->regs.ss = 0x600 >> 4;
state->regs.esp = (0x1000 - 0x600);
state->regs.cs = get_int_seg(vec);
state->regs.eip = get_int_off(vec);
state->if_flag = 0;
pushw(&state->regs, state->regs.flags);
pushw(&state->regs, 0x0000);
pushw(&state->regs, 0x0600);
ip = (int8 *)0x600;
*ip++ = INTn;
*ip++ = RETURN_TO_32_INT;
thread->fault_callback = &vm86_fault_callback;
do {
ret = x86_vm86_enter(&state->regs);
if (ret != B_OK)
break;
emuState = emulate(state);
} while (emuState == 0);
thread->fault_callback = NULL;
return emuState < 0 ? B_BAD_DATA : ret;
}