/* * 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 #include #include #include #include #include #include #include //#define TRACE_VM86 #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: "); /* 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"); uint32 flags = state->regs.flags; /* store real IF */ 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); /* 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(); TRACE("Unhandled fault at 0x%08x touching 0x%08x while %s\n", faultAddress, address, isWrite?"writing":"reading"); // 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->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; } // 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; 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; // 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, false); 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; } /*! 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) vm_delete_area(team->id, state->bios_area, true); if (state->ram_area > B_OK) vm_delete_area(team->id, state->ram_area, true); } /*! 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. 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; // 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; }