Last patch of the vm86 patch series from Jan Klötzke - thanks!:

* The new function vm86_do_int(struct vm86_state *state, uint8 vec) provides a
  facility to call BIOS interupt handlers. The function must only be called from
  a user thread context because the lower 1MB of the address space is used.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25610 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2008-05-22 13:54:28 +00:00
parent 7da0a81c0e
commit 15173df4e9
7 changed files with 828 additions and 0 deletions

View File

@ -157,6 +157,37 @@ struct iframe {
uint32 user_ss;
};
struct vm86_iframe {
uint32 type; // iframe type
uint32 __null_gs;
uint32 __null_fs;
uint32 __null_es;
uint32 __null_ds;
uint32 edi;
uint32 esi;
uint32 ebp;
uint32 __kern_esp;
uint32 ebx;
uint32 edx;
uint32 ecx;
uint32 eax;
uint32 orig_eax;
uint32 orig_edx;
uint32 vector;
uint32 error_code;
uint32 eip;
uint16 cs, __csh;
uint32 flags;
uint32 esp;
uint16 ss, __ssh;
/* vm86 mode specific part */
uint16 es, __esh;
uint16 ds, __dsh;
uint16 fs, __fsh;
uint16 gs, __gsh;
};
#define IFRAME_IS_USER(f) ( ((f)->cs == USER_CODE_SEG) \
|| (((f)->flags & 0x20000) != 0 ))
#define IFRAME_IS_VM86(f) ( ((f)->flags & 0x20000) != 0 )

View File

@ -0,0 +1,38 @@
/*
* Copyright 2008 Jan Klötzke
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef _VM86_H
#define _VM86_H
#include <OS.h>
#include <arch_cpu.h>
#ifdef __cplusplus
extern "C" {
#endif
#define VM86_MIN_RAM_SIZE 0x1000
#define RETURN_TO_32_INT 255
struct vm86_state {
struct vm86_iframe regs;
area_id bios_area;
area_id ram_area;
unsigned int if_flag : 1;
};
status_t x86_vm86_enter(struct vm86_iframe *frame);
void x86_vm86_return(struct vm86_iframe *frame, status_t retval);
status_t vm86_prepare(struct vm86_state *state, unsigned int ram_size);
void vm86_cleanup(struct vm86_state *state);
status_t vm86_do_int(struct vm86_state *state, uint8 vec);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -33,6 +33,7 @@ KernelStaticLibrary libx86 :
bios.cpp
cpuid.S
syscall.S
vm86.cpp
generic_vm_physical_page_mapper.cpp
:

View File

@ -23,6 +23,7 @@
#include <arch/vm.h>
#include <arch/x86/descriptors.h>
#include <arch/x86/vm86.h>
#include "interrupts.h"
@ -355,6 +356,12 @@ unexpected_exception(struct iframe* frame)
debug_exception_type type;
int signal;
if (IFRAME_IS_VM86(frame)) {
x86_vm86_return((struct vm86_iframe *)frame, (frame->vector == 13) ?
B_OK : B_ERROR);
// won't get here
}
switch (frame->vector) {
case 0: // Divide Error Exception (#DE)
type = B_DIVIDE_ERROR;

View File

@ -246,6 +246,8 @@ int_bottom:
movl %esp, %ebp // frame pointer is the iframe
testl $0x20000, IFRAME_flags(%ebp) // VM86 mode
jnz int_bottom_vm86
cmp $USER_CODE_SEG, IFRAME_cs(%ebp)
je int_bottom_user
@ -292,6 +294,33 @@ int_bottom_user:
POP_IFRAME_AND_RETURN()
int_bottom_vm86:
movl $KERNEL_DATA_SEG,%eax
cld
movl %eax,%ds
movl %eax,%es
// update the thread's user time
movl %dr3, %edi // thread pointer
cli
UPDATE_THREAD_USER_TIME()
// leave interrupts disabled -- the handler will enable them, if
// necessary
pushl %ebp
movl IFRAME_vector(%ebp), %eax
call *gInterruptHandlerTable(, %eax, 4)
// update the thread's kernel time and return
cli
UPDATE_THREAD_KERNEL_TIME()
lea 20(%ebp), %esp;
popa;
addl $16,%esp;
iret
// test interrupt handler for performance measurements
.align 16
.globl trap98
@ -537,3 +566,76 @@ FUNCTION(i386_restore_frame_from_syscall):
// update the thread's kernel time and return
UPDATE_THREAD_KERNEL_TIME()
POP_IFRAME_AND_RETURN()
/* status_t x86_vm86_enter(struct vm86_iframe *frame) */
FUNCTION(x86_vm86_enter):
// save critical registers
pushf
pushl %edi
pushl %esi
pushl %ebp
pushl %ebx
push %gs
push %fs
// get pointers
movl 32(%esp), %esi // vm86 iframe
push %esi // ... store iframe addr for x86_vm86_return
movl %dr3, %edi // struct thread
// make sure eflags are in right shape
movl VM86_IFRAME_flags(%esi), %eax
andl $0x200CD5, %eax // leave ID,OF,DF,SF,ZF,AF,PF,CF flags
orl $0x20202, %eax // set VM and IF flags (+10b)
movl %eax, VM86_IFRAME_flags(%esi)
// update kernel_stack_top and tss.esp0
pushl THREAD_kernel_stack_top(%edi)
movl %esp, THREAD_kernel_stack_top(%edi)
pushl %esp
call i386_set_tss_and_kstack
// go to vm86 mode
cli
UPDATE_THREAD_KERNEL_TIME()
lea 20(%esi), %esp
popa
addl $16, %esp
iret
/* void x86_vm86_return(struct vm86_iframe *frame, status_t retval) */
FUNCTION(x86_vm86_return):
// set stack to where x86_vm86_enter was left
movl 8(%esp), %ebx
movl 4(%esp), %esi
cli
movl %esi, %esp
addl $VM86_IFRAME_sizeof, %esp
// save old iframe
popl %eax // old kernel stack top
popl %edi
movl $(VM86_IFRAME_sizeof >> 2), %ecx
cld
rep movsl
// adjust kernel_stack_top and tss.esp0
movl %dr3, %edi
movl %eax, THREAD_kernel_stack_top(%edi)
push %eax
call i386_set_tss_and_kstack
addl $4, %esp
// restore registers
movl %ebx, %eax
pop %fs
pop %gs
popl %ebx
popl %ebp
popl %esi
popl %edi
popf
ret

View File

@ -45,6 +45,10 @@ dummy()
DEFINE_OFFSET_MACRO(IFRAME, iframe, flags);
DEFINE_OFFSET_MACRO(IFRAME, iframe, user_esp);
// struct vm86_iframe
DEFINE_SIZEOF_MACRO(VM86_IFRAME, vm86_iframe);
DEFINE_OFFSET_MACRO(VM86_IFRAME, vm86_iframe, flags);
// struct syscall_info
DEFINE_SIZEOF_MACRO(SYSCALL_INFO, syscall_info);
DEFINE_OFFSET_MACRO(SYSCALL_INFO, syscall_info, function);

View File

@ -0,0 +1,645 @@
/*
* 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>
//#define TRACE_VM86
#ifdef TRACE_VM86
# define TRACE(x...) kprintf("[vm86] " x)
# define TRACE_NP(x...) kprintf(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 (1 << 9)
#define DIRECTION_FLAG (1 << 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: ");
/* handle prefixes */
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:
/* these prefixes are just ignored */
TRACE_NP("IGN(0x%02x) ", *instruction);
state->regs.eip++;
instruction++;
break;
default:
ret = 1;
}
} while (!ret);
/* handle actual instruction */
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;
/* store real IF */
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);
/* restore IF */
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");
uint16 flags = state->regs.flags & I_FLAG;
/* store real IF */
flags |= (uint16)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);
/* restore IF */
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();
// we shouldn't have unhandled page faults in vm86 mode
x86_vm86_return((struct vm86_iframe *)frame, B_BAD_ADDRESS);
// not reached
return false;
}
// #pragma mark - exported interface
/*! Prepare the thread to execute BIOS code in virtual 8086 mode. Initializes
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;
// create RAM area
if (ramSize < VM86_MIN_RAM_SIZE)
ramSize = VM86_MIN_RAM_SIZE;
void *address = (void *)0;
state->ram_area = create_area_etc(team, "dos", &address, B_EXACT_ADDRESS,
ramSize, B_NO_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA
| B_READ_AREA | B_WRITE_AREA);
if (state->ram_area < B_OK) {
ret = state->ram_area;
goto error;
}
// copy int vectors and BIOS data area
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;
goto error;
}
ret = user_memcpy((void *)0, address, 0x502);
*((uint32 *)0) = 0xDEADBEEF;
delete_area(vectors);
if (ret != B_OK)
goto error;
// map vga/bios area
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;
goto error;
}
return B_OK;
error:
if (state->bios_area > B_OK)
delete_area_etc(team, state->bios_area);
if (state->ram_area > B_OK)
delete_area_etc(team, state->ram_area);
return ret;
}
/*! Free ressources which were allocated by vm86_prepare().
*/
extern "C" void
vm86_cleanup(struct vm86_state *state)
{
struct team *team = thread_get_current_thread()->team;
if (state->bios_area > B_OK)
delete_area_etc(team, state->bios_area);
if (state->ram_area > B_OK)
delete_area_etc(team, state->ram_area);
}
/*! Execute a BIOS call of interrupt \a vec. The given \a state must be
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.
*/
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;
// prepare environment
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); /* CS */
pushw(&state->regs, 0x0600); /* IP */
ip = (int8 *)0x600;
*ip++ = INTn;
*ip++ = RETURN_TO_32_INT;
// execute interrupt
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;
}