/*
 * vm86.c: A vm86 emulator. The main purpose of this emulator is to do as
 * little work as possible.
 *
 * Leendert van Doorn, leendert@watson.ibm.com
 * Copyright (c) 2005-2006, International Business Machines Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307 USA.
 */
#include "vm86.h"
#include "util.h"
#include "machine.h"

#define	HIGHMEM		(1 << 20)		/* 1MB */
#define	MASK16(v)	((v) & 0xFFFF)

#define	DATA32		0x0001
#define	ADDR32		0x0002
#define	SEG_CS		0x0004
#define	SEG_DS		0x0008
#define	SEG_ES		0x0010
#define	SEG_SS		0x0020
#define	SEG_FS		0x0040
#define	SEG_GS		0x0080

static unsigned prev_eip = 0;
enum vm86_mode mode = 0;

static struct regs saved_rm_regs;

#ifdef DEBUG
int traceset = 0;

char *states[] = {
	"<VM86_REAL>",
	"<VM86_REAL_TO_PROTECTED>",
	"<VM86_PROTECTED_TO_REAL>",
	"<VM86_PROTECTED>"
};

static char *rnames[] = { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di" };
#endif /* DEBUG */

#define PDE_PS				(1 << 7)
#define PT_ENTRY_PRESENT	0x1

/* We only support access to <=4G physical memory due to 1:1 mapping */
static uint64_t
guest_linear_to_phys(uint32_t base)
{
	uint32_t gcr3 = oldctx.cr3;
	uint64_t l2_mfn;
	uint64_t l1_mfn;
	uint64_t l0_mfn;

	if (!(oldctx.cr0 & CR0_PG))
		return base;

	if (!(oldctx.cr4 & CR4_PAE)) {
		l1_mfn = ((uint32_t *)(long)gcr3)[(base >> 22) & 0x3ff];
		if (!(l1_mfn & PT_ENTRY_PRESENT))
			panic("l2 entry not present\n");

		if ((oldctx.cr4 & CR4_PSE) && (l1_mfn & PDE_PS)) {
			l0_mfn = l1_mfn & 0xffc00000;
			return l0_mfn + (base & 0x3fffff);
		}

		l1_mfn &= 0xfffff000;

		l0_mfn = ((uint32_t *)(long)l1_mfn)[(base >> 12) & 0x3ff];
		if (!(l0_mfn & PT_ENTRY_PRESENT))
			panic("l1 entry not present\n");
		l0_mfn &= 0xfffff000;

		return l0_mfn + (base & 0xfff);
	} else {
		l2_mfn = ((uint64_t *)(long)gcr3)[(base >> 30) & 0x3];
		if (!(l2_mfn & PT_ENTRY_PRESENT))
			panic("l3 entry not present\n");
		l2_mfn &= 0xffffff000ULL;

		if (l2_mfn & 0xf00000000ULL) {
			printf("l2 page above 4G\n");
			cpuid_addr_value(l2_mfn + 8 * ((base >> 21) & 0x1ff), &l1_mfn);
		} else
			l1_mfn = ((uint64_t *)(long)l2_mfn)[(base >> 21) & 0x1ff];
		if (!(l1_mfn & PT_ENTRY_PRESENT))
			panic("l2 entry not present\n");

		if (l1_mfn & PDE_PS) { /* CR4.PSE is ignored in PAE mode */
			l0_mfn = l1_mfn & 0xfffe00000ULL;
			return l0_mfn + (base & 0x1fffff);
		}

		l1_mfn &= 0xffffff000ULL;

		if (l1_mfn & 0xf00000000ULL) {
			printf("l1 page above 4G\n");
			cpuid_addr_value(l1_mfn + 8 * ((base >> 12) & 0x1ff), &l0_mfn);
		} else
			l0_mfn = ((uint64_t *)(long)l1_mfn)[(base >> 12) & 0x1ff];
		if (!(l0_mfn & PT_ENTRY_PRESENT))
			panic("l1 entry not present\n");

		l0_mfn &= 0xffffff000ULL;

		return l0_mfn + (base & 0xfff);
	}
}

static unsigned
address(struct regs *regs, unsigned seg, unsigned off)
{
	uint64_t gdt_phys_base;
	unsigned long long entry;
	unsigned seg_base, seg_limit;
	unsigned entry_low, entry_high;

	if (seg == 0) {
		if (mode == VM86_REAL || mode == VM86_REAL_TO_PROTECTED)
			return off;
		else
			panic("segment is zero, but not in real mode!\n");
	}

	if (mode == VM86_REAL || seg > oldctx.gdtr_limit ||
		(mode == VM86_REAL_TO_PROTECTED && regs->cs == seg))
		return ((seg & 0xFFFF) << 4) + off;

	gdt_phys_base = guest_linear_to_phys(oldctx.gdtr_base);
	if (gdt_phys_base != (uint32_t)gdt_phys_base) {
		printf("gdt base address above 4G\n");
		cpuid_addr_value(gdt_phys_base + 8 * (seg >> 3), &entry);
	} else
		entry = ((unsigned long long *)(long)gdt_phys_base)[seg >> 3];

	entry_high = entry >> 32;
	entry_low = entry & 0xFFFFFFFF;

	seg_base  = (entry_high & 0xFF000000) | ((entry >> 16) & 0xFFFFFF);
	seg_limit = (entry_high & 0xF0000) | (entry_low & 0xFFFF);

	if (entry_high & 0x8000 &&
		((entry_high & 0x800000 && off >> 12 <= seg_limit) ||
		(!(entry_high & 0x800000) && off <= seg_limit)))
		return seg_base + off;

	panic("should never reach here in function address():\n\t"
		  "entry=0x%08x%08x, mode=%d, seg=0x%08x, offset=0x%08x\n",
		  entry_high, entry_low, mode, seg, off);

	return 0;
}

#ifdef DEBUG
void
trace(struct regs *regs, int adjust, char *fmt, ...)
{
	unsigned off = regs->eip - adjust;
	va_list ap;

	if ((traceset & (1 << mode)) &&
		(mode == VM86_REAL_TO_PROTECTED || mode == VM86_REAL)) {
		/* 16-bit, seg:off addressing */
		unsigned addr = address(regs, regs->cs, off);
		printf("0x%08x: 0x%x:0x%04x ", addr, regs->cs, off);
		printf("(%d) ", mode);
		va_start(ap, fmt);
		vprintf(fmt, ap);
		va_end(ap);
		printf("\n");
	}
	if ((traceset & (1 << mode)) &&
		(mode == VM86_PROTECTED_TO_REAL || mode == VM86_PROTECTED)) {
		/* 16-bit, gdt addressing */
		unsigned addr = address(regs, regs->cs, off);
		printf("0x%08x: 0x%x:0x%08x ", addr, regs->cs, off);
		printf("(%d) ", mode);
		va_start(ap, fmt);
		vprintf(fmt, ap);
		va_end(ap);
		printf("\n");
	}
}
#endif /* DEBUG */

static inline unsigned
read32(unsigned addr)
{
	return *(unsigned long *) addr;
}

static inline unsigned
read16(unsigned addr)
{
	return *(unsigned short *) addr;
}

static inline unsigned
read8(unsigned addr)
{
	return *(unsigned char *) addr;
}

static inline void
write32(unsigned addr, unsigned value)
{
	*(unsigned long *) addr = value;
}

static inline void
write16(unsigned addr, unsigned value)
{
	*(unsigned short *) addr = value;
}

static inline void
write8(unsigned addr, unsigned value)
{
	*(unsigned char *) addr = value;
}

static inline void
push32(struct regs *regs, unsigned value)
{
	regs->uesp -= 4;
	write32(address(regs, regs->uss, MASK16(regs->uesp)), value);
}

static inline void
push16(struct regs *regs, unsigned value)
{
	regs->uesp -= 2;
	write16(address(regs, regs->uss, MASK16(regs->uesp)), value);
}

static inline unsigned
pop32(struct regs *regs)
{
	unsigned value = read32(address(regs, regs->uss, MASK16(regs->uesp)));
	regs->uesp += 4;
	return value;
}

static inline unsigned
pop16(struct regs *regs)
{
	unsigned value = read16(address(regs, regs->uss, MASK16(regs->uesp)));
	regs->uesp += 2;
	return value;
}

static inline unsigned
fetch32(struct regs *regs)
{
	unsigned addr = address(regs, regs->cs, MASK16(regs->eip));

	regs->eip += 4;
	return read32(addr);
}

static inline unsigned
fetch16(struct regs *regs)
{
	unsigned addr = address(regs, regs->cs, MASK16(regs->eip));

	regs->eip += 2;
	return read16(addr);
}

static inline unsigned
fetch8(struct regs *regs)
{
	unsigned addr = address(regs, regs->cs, MASK16(regs->eip));

	regs->eip++;
	return read8(addr);
}

static unsigned
getreg32(struct regs *regs, int r)
{
	switch (r & 7) {
	case 0: return regs->eax;
	case 1: return regs->ecx;
	case 2: return regs->edx;
	case 3: return regs->ebx;
	case 4: return regs->uesp;
	case 5: return regs->ebp;
	case 6: return regs->esi;
	case 7: return regs->edi;
	}
	return ~0;
}

static unsigned
getreg16(struct regs *regs, int r)
{
	return MASK16(getreg32(regs, r));
}

static unsigned
getreg8(struct regs *regs, int r)
{
	switch (r & 7) {
	case 0: return regs->eax & 0xFF; /* al */
	case 1: return regs->ecx & 0xFF; /* cl */
	case 2: return regs->edx & 0xFF; /* dl */
	case 3: return regs->ebx & 0xFF; /* bl */
	case 4: return (regs->eax >> 8) & 0xFF; /* ah */
	case 5: return (regs->ecx >> 8) & 0xFF; /* ch */
	case 6: return (regs->edx >> 8) & 0xFF; /* dh */
	case 7: return (regs->ebx >> 8) & 0xFF; /* bh */
	}
	return ~0;
}

static void
setreg32(struct regs *regs, int r, unsigned v)
{
	switch (r & 7) {
	case 0: regs->eax = v; break;
	case 1: regs->ecx = v; break;
	case 2: regs->edx = v; break;
	case 3: regs->ebx = v; break;
	case 4: regs->uesp = v; break;
	case 5: regs->ebp = v; break;
	case 6: regs->esi = v; break;
	case 7: regs->edi = v; break;
	}
}

static void
setreg16(struct regs *regs, int r, unsigned v)
{
	setreg32(regs, r, (getreg32(regs, r) & ~0xFFFF) | MASK16(v));
}

static void
setreg8(struct regs *regs, int r, unsigned v)
{
	v &= 0xFF;
	switch (r & 7) {
	case 0: regs->eax = (regs->eax & ~0xFF) | v; break;
	case 1: regs->ecx = (regs->ecx & ~0xFF) | v; break;
	case 2: regs->edx = (regs->edx & ~0xFF) | v; break;
	case 3: regs->ebx = (regs->ebx & ~0xFF) | v; break;
	case 4: regs->eax = (regs->eax & ~0xFF00) | (v << 8); break;
	case 5: regs->ecx = (regs->ecx & ~0xFF00) | (v << 8); break;
	case 6: regs->edx = (regs->edx & ~0xFF00) | (v << 8); break;
	case 7: regs->ebx = (regs->ebx & ~0xFF00) | (v << 8); break;
	}
}

static unsigned
segment(unsigned prefix, struct regs *regs, unsigned seg)
{
	if (prefix & SEG_ES)
		seg = regs->ves;
	if (prefix & SEG_DS)
		seg = regs->vds;
	if (prefix & SEG_CS)
		seg = regs->cs;
	if (prefix & SEG_SS)
		seg = regs->uss;
	if (prefix & SEG_FS)
		seg = regs->vfs;
	if (prefix & SEG_GS)
		seg = regs->vgs;
	return seg;
}

static unsigned
sib(struct regs *regs, int mod, unsigned byte)
{
	unsigned scale = (byte >> 6) & 3;
	int index = (byte >> 3) & 7;
	int base = byte & 7;
	unsigned addr = 0;

	switch (mod) {
	case 0:
		if (base == 5)
			addr = fetch32(regs);
		else
			addr = getreg32(regs, base);
		break;
	case 1:
		addr = getreg32(regs, base) + (char) fetch8(regs);
		break;
	case 2:
		addr = getreg32(regs, base) + fetch32(regs);
		break;
	}

	if (index != 4)
		addr += getreg32(regs, index) << scale;

	return addr;
}

/*
 * Operand (modrm) decode
 */
static unsigned
operand(unsigned prefix, struct regs *regs, unsigned modrm)
{
	int mod, disp = 0, seg;

	seg = segment(prefix, regs, regs->vds);

	if (prefix & ADDR32) { /* 32-bit addressing */
		switch ((mod = (modrm >> 6) & 3)) {
		case 0:
			switch (modrm & 7) {
			case 0: return address(regs, seg, regs->eax);
			case 1: return address(regs, seg, regs->ecx);
			case 2: return address(regs, seg, regs->edx);
			case 3: return address(regs, seg, regs->ebx);
			case 4: return address(regs, seg,
						   sib(regs, mod, fetch8(regs)));
			case 5: return address(regs, seg, fetch32(regs));
			case 6: return address(regs, seg, regs->esi);
			case 7: return address(regs, seg, regs->edi);
			}
			break;
		case 1:
		case 2:
			if ((modrm & 7) != 4) {
				if (mod == 1)
					disp = (char) fetch8(regs);
				else
					disp = (int) fetch32(regs);
			}
			switch (modrm & 7) {
			case 0: return address(regs, seg, regs->eax + disp);
			case 1: return address(regs, seg, regs->ecx + disp);
			case 2: return address(regs, seg, regs->edx + disp);
			case 3: return address(regs, seg, regs->ebx + disp);
			case 4: return address(regs, seg,
						   sib(regs, mod, fetch8(regs)));
			case 5: return address(regs, seg, regs->ebp + disp);
			case 6: return address(regs, seg, regs->esi + disp);
			case 7: return address(regs, seg, regs->edi + disp);
			}
			break;
		case 3:
			return getreg32(regs, modrm);
		}
	} else { /* 16-bit addressing */
		switch ((mod = (modrm >> 6) & 3)) {
		case 0:
			switch (modrm & 7) {
			case 0: return address(regs, seg, MASK16(regs->ebx) +
					MASK16(regs->esi));
			case 1: return address(regs, seg, MASK16(regs->ebx) +
					MASK16(regs->edi));
			case 2: return address(regs, seg, MASK16(regs->ebp) +
					MASK16(regs->esi));
			case 3: return address(regs, seg, MASK16(regs->ebp) +
					MASK16(regs->edi));
			case 4: return address(regs, seg, MASK16(regs->esi));
			case 5: return address(regs, seg, MASK16(regs->edi));
			case 6: return address(regs, seg, fetch16(regs));
			case 7: return address(regs, seg, MASK16(regs->ebx));
			}
			break;
		case 1:
		case 2:
			if (mod == 1)
				disp = (char) fetch8(regs);
			else
				disp = (int) fetch16(regs);
			switch (modrm & 7) {
			case 0: return address(regs, seg, MASK16(regs->ebx) +
					MASK16(regs->esi) + disp);
			case 1: return address(regs, seg, MASK16(regs->ebx) +
					MASK16(regs->edi) + disp);
			case 2: return address(regs, seg, MASK16(regs->ebp) +
					MASK16(regs->esi) + disp);
			case 3: return address(regs, seg, MASK16(regs->ebp) +
					MASK16(regs->edi) + disp);
			case 4: return address(regs, seg,
					MASK16(regs->esi) + disp);
			case 5: return address(regs, seg,
					MASK16(regs->edi) + disp);
			case 6: return address(regs, seg,
					MASK16(regs->ebp) + disp);
			case 7: return address(regs, seg,
					MASK16(regs->ebx) + disp);
			}
			break;
		case 3:
			return getreg16(regs, modrm);
		}
	}

	return 0;
}

/*
 * Load new IDT
 */
static int
lidt(struct regs *regs, unsigned prefix, unsigned modrm)
{
	unsigned eip = regs->eip - 3;
	unsigned addr = operand(prefix, regs, modrm);

	oldctx.idtr_limit = ((struct dtr *) addr)->size;
	if ((prefix & DATA32) == 0)
		oldctx.idtr_base = ((struct dtr *) addr)->base & 0xFFFFFF;
	else
		oldctx.idtr_base = ((struct dtr *) addr)->base;
	TRACE((regs, regs->eip - eip, "lidt 0x%x <%d, 0x%x>",
		addr, oldctx.idtr_limit, oldctx.idtr_base));

	return 1;
}

/*
 * Load new GDT
 */
static int
lgdt(struct regs *regs, unsigned prefix, unsigned modrm)
{
	unsigned eip = regs->eip - 3;
	unsigned addr = operand(prefix, regs, modrm);

	oldctx.gdtr_limit = ((struct dtr *) addr)->size;
	if ((prefix & DATA32) == 0)
		oldctx.gdtr_base = ((struct dtr *) addr)->base & 0xFFFFFF;
	else
		oldctx.gdtr_base = ((struct dtr *) addr)->base;
	TRACE((regs, regs->eip - eip, "lgdt 0x%x <%d, 0x%x>",
		addr, oldctx.gdtr_limit, oldctx.gdtr_base));

	return 1;
}

/*
 * Modify CR0 either through an lmsw instruction.
 */
static int
lmsw(struct regs *regs, unsigned prefix, unsigned modrm)
{
	unsigned eip = regs->eip - 3;
	unsigned ax = operand(prefix, regs, modrm) & 0xF;
	unsigned cr0 = (oldctx.cr0 & 0xFFFFFFF0) | ax;

	TRACE((regs, regs->eip - eip, "lmsw 0x%x", ax));
#ifndef TEST
	oldctx.cr0 = cr0 | CR0_PE | CR0_NE;
#else
	oldctx.cr0 = cr0 | CR0_PE | CR0_NE | CR0_PG;
#endif
	if (cr0 & CR0_PE)
		set_mode(regs, VM86_REAL_TO_PROTECTED);

	return 1;
}

/*
 * We need to handle moves that address memory beyond the 64KB segment
 * limit that VM8086 mode enforces.
 */
static int
movr(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned eip = regs->eip - 1;
	unsigned modrm = fetch8(regs);
	unsigned addr = operand(prefix, regs, modrm);
	unsigned val, r = (modrm >> 3) & 7;

	if ((modrm & 0xC0) == 0xC0) /* no registers */
		return 0;

	switch (opc) {
	case 0x88: /* addr32 mov r8, r/m8 */
		val = getreg8(regs, r);
		TRACE((regs, regs->eip - eip,
			"movb %%e%s, *0x%x", rnames[r], addr));
		write8(addr, val);
		break;

	case 0x8A: /* addr32 mov r/m8, r8 */
		TRACE((regs, regs->eip - eip,
			"movb *0x%x, %%%s", addr, rnames[r]));
		setreg8(regs, r, read8(addr));
		break;

	case 0x89: /* addr32 mov r16, r/m16 */
		val = getreg32(regs, r);
		if (prefix & DATA32) {
			TRACE((regs, regs->eip - eip,
				"movl %%e%s, *0x%x", rnames[r], addr));
			write32(addr, val);
		} else {
			TRACE((regs, regs->eip - eip,
				"movw %%%s, *0x%x", rnames[r], addr));
			write16(addr, MASK16(val));
		}
		break;

	case 0x8B: /* addr32 mov r/m16, r16 */
		if (prefix & DATA32) {
			TRACE((regs, regs->eip - eip,
				"movl *0x%x, %%e%s", addr, rnames[r]));
			setreg32(regs, r, read32(addr));
		} else {
			TRACE((regs, regs->eip - eip,
				"movw *0x%x, %%%s", addr, rnames[r]));
			setreg16(regs, r, read16(addr));
		}
		break;

	case 0xC6: /* addr32 movb $imm, r/m8 */
		if ((modrm >> 3) & 7)
			return 0;
		val = fetch8(regs);
		write8(addr, val);
		TRACE((regs, regs->eip - eip, "movb $0x%x, *0x%x",
							val, addr));
		break;
	}
	return 1;
}

/*
 * Move to and from a control register.
 */
static int
movcr(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned eip = regs->eip - 2;
	unsigned modrm = fetch8(regs);
	unsigned cr = (modrm >> 3) & 7;

	if ((modrm & 0xC0) != 0xC0) /* only registers */
		return 0;

	switch (opc) {
	case 0x20: /* mov Rd, Cd */
		TRACE((regs, regs->eip - eip, "movl %%cr%d, %%eax", cr));
		switch (cr) {
		case 0:
#ifndef TEST
			setreg32(regs, modrm,
				oldctx.cr0 & ~(CR0_PE | CR0_NE));
#else
			setreg32(regs, modrm,
				oldctx.cr0 & ~(CR0_PE | CR0_NE | CR0_PG));
#endif
			break;
		case 2:
			setreg32(regs, modrm, get_cr2());
			break;
		case 3:
			setreg32(regs, modrm, oldctx.cr3);
			break;
		case 4:
			setreg32(regs, modrm, oldctx.cr4);
			break;
		}
		break;
	case 0x22: /* mov Cd, Rd */
		TRACE((regs, regs->eip - eip, "movl %%eax, %%cr%d", cr));
		switch (cr) {
		case 0:
			oldctx.cr0 = getreg32(regs, modrm) | (CR0_PE | CR0_NE);
#ifdef TEST
			oldctx.cr0 |= CR0_PG;
#endif
			if (getreg32(regs, modrm) & CR0_PE)
				set_mode(regs, VM86_REAL_TO_PROTECTED);
			else
				set_mode(regs, VM86_REAL);
			break;
		case 3:
			oldctx.cr3 = getreg32(regs, modrm);
			break;
		case 4:
			oldctx.cr4 = getreg32(regs, modrm);
			break;
		}
		break;
	}

	return 1;
}

static inline void set_eflags_ZF(unsigned mask, unsigned v1, struct regs *regs)
{
	if ((v1 & mask) == 0)
		regs->eflags |= EFLAGS_ZF;
	else
		regs->eflags &= ~EFLAGS_ZF;
}

/*
 * We need to handle cmp opcodes that address memory beyond the 64KB
 * segment limit that VM8086 mode enforces.
 */
static int
cmp(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned eip = regs->eip - 1;
	unsigned modrm = fetch8(regs);
	unsigned addr = operand(prefix, regs, modrm);
	unsigned diff, val, r = (modrm >> 3) & 7;

	if ((modrm & 0xC0) == 0xC0) /* no registers */
		return 0;

	switch (opc) {
	case 0x39: /* addr32 cmp r16, r/m16 */
		val = getreg32(regs, r);
		if (prefix & DATA32) {
			diff = read32(addr) - val;
			set_eflags_ZF(~0, diff, regs);

			TRACE((regs, regs->eip - eip,
				"cmp %%e%s, *0x%x (0x%x)",
				rnames[r], addr, diff));
		} else {
			diff = read16(addr) - val;
			set_eflags_ZF(0xFFFF, diff, regs);

			TRACE((regs, regs->eip - eip,
				"cmp %%%s, *0x%x (0x%x)",
				rnames[r], addr, diff));
		}
		break;

	/* other cmp opcodes ... */
	}
	return 1;
}

/*
 * We need to handle test opcodes that address memory beyond the 64KB
 * segment limit that VM8086 mode enforces.
 */
static int
test(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned eip = regs->eip - 1;
	unsigned modrm = fetch8(regs);
	unsigned addr = operand(prefix, regs, modrm);
	unsigned val, diff;

	if ((modrm & 0xC0) == 0xC0) /* no registers */
		return 0;

	switch (opc) {
	case 0xF6: /* testb $imm, r/m8 */
		if ((modrm >> 3) & 7)
			return 0;
		val = fetch8(regs);
		diff = read8(addr) & val;
		set_eflags_ZF(0xFF, diff, regs);

		TRACE((regs, regs->eip - eip, "testb $0x%x, *0x%x (0x%x)",
							val, addr, diff));
		break;

	/* other test opcodes ... */
	}

	return 1;
}

/*
 * We need to handle pop opcodes that address memory beyond the 64KB
 * segment limit that VM8086 mode enforces.
 */
static int
pop(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned eip = regs->eip - 1;
	unsigned modrm = fetch8(regs);
	unsigned addr = operand(prefix, regs, modrm);

	if ((modrm & 0xC0) == 0xC0) /* no registers */
		return 0;

	switch (opc) {
	case 0x8F: /* pop r/m16 */
		if ((modrm >> 3) & 7)
			return 0;
		if (prefix & DATA32)
			write32(addr, pop32(regs));
		else
			write16(addr, pop16(regs));
		TRACE((regs, regs->eip - eip, "pop *0x%x", addr));
		break;

	/* other pop opcodes ... */
	}

	return 1;
}

static int
mov_to_seg(struct regs *regs, unsigned prefix, unsigned opc)
{
	unsigned modrm = fetch8(regs);

	/* Only need to emulate segment loads in real->protected mode. */
	if (mode != VM86_REAL_TO_PROTECTED)
		return 0;

	/* Register source only. */
	if ((modrm & 0xC0) != 0xC0)
		goto fail;

	switch ((modrm & 0x38) >> 3) {
	case 0: /* es */
		regs->ves = getreg16(regs, modrm);
		saved_rm_regs.ves = 0;
		oldctx.es_sel = regs->ves;
		return 1;

	/* case 1: cs */

	case 2: /* ss */
		regs->uss = getreg16(regs, modrm);
		saved_rm_regs.uss = 0;
		oldctx.ss_sel = regs->uss;
		return 1;
	case 3: /* ds */
		regs->vds = getreg16(regs, modrm);
		saved_rm_regs.vds = 0;
		oldctx.ds_sel = regs->vds;
		return 1;
	case 4: /* fs */
		regs->vfs = getreg16(regs, modrm);
		saved_rm_regs.vfs = 0;
		oldctx.fs_sel = regs->vfs;
		return 1;
	case 5: /* gs */
		regs->vgs = getreg16(regs, modrm);
		saved_rm_regs.vgs = 0;
		oldctx.gs_sel = regs->vgs;
		return 1;
	}

 fail:
	printf("%s:%d: missed opcode %02x %02x\n",
		   __FUNCTION__, __LINE__, opc, modrm);
	return 0;
}

/*
 * Emulate a segment load in protected mode
 */
static int
load_seg(unsigned long sel, uint32_t *base, uint32_t *limit, union vmcs_arbytes *arbytes)
{
	uint64_t gdt_phys_base;
	unsigned long long entry;

	/* protected mode: use seg as index into gdt */
	if (sel > oldctx.gdtr_limit)
		return 0;

	if (sel == 0) {
		arbytes->fields.null_bit = 1;
		return 1;
	}

	gdt_phys_base = guest_linear_to_phys(oldctx.gdtr_base);
	if (gdt_phys_base != (uint32_t)gdt_phys_base) {
		printf("gdt base address above 4G\n");
		cpuid_addr_value(gdt_phys_base + 8 * (sel >> 3), &entry);
	} else
		entry = ((unsigned long long *)(long)gdt_phys_base)[sel >> 3];

	/* Check the P bit first */
	if (!((entry >> (15+32)) & 0x1) && sel != 0)
		return 0;

	*base =  (((entry >> (56-24)) & 0xFF000000) |
		  ((entry >> (32-16)) & 0x00FF0000) |
		  ((entry >> (   16)) & 0x0000FFFF));
	*limit = (((entry >> (48-16)) & 0x000F0000) |
		  (entry & 0x0000FFFF));

	arbytes->bytes = 0;
	arbytes->fields.seg_type = (entry >> (8+32)) & 0xF; /* TYPE */
	arbytes->fields.s = (entry >> (12+32)) & 0x1; /* S */
	if (arbytes->fields.s)
		arbytes->fields.seg_type |= 1; /* accessed */
	arbytes->fields.dpl = (entry >> (13+32)) & 0x3; /* DPL */
	arbytes->fields.p = (entry >> (15+32)) & 0x1; /* P */
	arbytes->fields.avl = (entry >> (20+32)) & 0x1; /* AVL */
	arbytes->fields.default_ops_size = (entry >> (22+32)) & 0x1; /* D */

	if (entry & (1ULL << (23+32))) { /* G */
		arbytes->fields.g = 1;
		*limit = (*limit << 12) | 0xFFF;
	}

	return 1;
}

/*
 * Emulate a protected mode segment load, falling back to clearing it if
 * the descriptor was invalid.
 */
static void
load_or_clear_seg(unsigned long sel, uint32_t *base, uint32_t *limit, union vmcs_arbytes *arbytes)
{
	if (!load_seg(sel, base, limit, arbytes))
		load_seg(0, base, limit, arbytes);
}


/*
 * Transition to protected mode
 */
static void
protected_mode(struct regs *regs)
{
	extern char stack_top[];

	regs->eflags &= ~(EFLAGS_TF|EFLAGS_VM);

	oldctx.eip = regs->eip;
	oldctx.esp = regs->uesp;
	oldctx.eflags = regs->eflags;

	/* reload all segment registers */
	if (!load_seg(regs->cs, &oldctx.cs_base,
				&oldctx.cs_limit, &oldctx.cs_arbytes))
		panic("Invalid %%cs=0x%x for protected mode\n", regs->cs);
	oldctx.cs_sel = regs->cs;

	load_or_clear_seg(oldctx.es_sel, &oldctx.es_base,
			  &oldctx.es_limit, &oldctx.es_arbytes);
	load_or_clear_seg(oldctx.ss_sel, &oldctx.ss_base,
			  &oldctx.ss_limit, &oldctx.ss_arbytes);
	load_or_clear_seg(oldctx.ds_sel, &oldctx.ds_base,
			  &oldctx.ds_limit, &oldctx.ds_arbytes);
	load_or_clear_seg(oldctx.fs_sel, &oldctx.fs_base,
			  &oldctx.fs_limit, &oldctx.fs_arbytes);
	load_or_clear_seg(oldctx.gs_sel, &oldctx.gs_base,
			  &oldctx.gs_limit, &oldctx.gs_arbytes);

	/* initialize jump environment to warp back to protected mode */
	regs->uss = DATA_SELECTOR;
	regs->uesp = (unsigned long)stack_top;
	regs->cs = CODE_SELECTOR;
	regs->eip = (unsigned long)switch_to_protected_mode;

	/* this should get us into 32-bit mode */
}

/*
 * Start real-mode emulation
 */
static void
real_mode(struct regs *regs)
{
	regs->eflags |= EFLAGS_VM | 0x02;

	/*
	 * When we transition from protected to real-mode and we
	 * have not reloaded the segment descriptors yet, they are
	 * interpreted as if they were in protect mode.
	 * We emulate this behavior by assuming that these memory
	 * reference are below 1MB and set %ss, %ds, %es accordingly.
	 */
	if (regs->uss != 0) {
		if (regs->uss >= HIGHMEM)
			panic("%%ss 0x%lx higher than 1MB", regs->uss);
		regs->uss = address(regs, regs->uss, 0) >> 4;
	} else {
		regs->uss = saved_rm_regs.uss;
	}
	if (regs->vds != 0) {
		if (regs->vds >= HIGHMEM)
			panic("%%ds 0x%lx higher than 1MB", regs->vds);
		regs->vds = address(regs, regs->vds, 0) >> 4;
	} else {
		regs->vds = saved_rm_regs.vds;
	}
	if (regs->ves != 0) {
		if (regs->ves >= HIGHMEM)
			panic("%%es 0x%lx higher than 1MB", regs->ves);
		regs->ves = address(regs, regs->ves, 0) >> 4;
	} else {
		regs->ves = saved_rm_regs.ves;
	}

	/* this should get us into 16-bit mode */
}

/*
 * This is the smarts of the emulator and handles the mode transitions. The
 * emulator handles 4 different modes. 1) VM86_REAL: emulated real-mode,
 * Just handle those instructions that are not supported under VM8086.
 * 2) VM86_REAL_TO_PROTECTED: going from real-mode to protected mode. In
 * this we single step through the instructions until we reload the
 * new %cs (some OSes do a lot of computations before reloading %cs). 2)
 * VM86_PROTECTED_TO_REAL when we are going from protected to real mode. In
 * this case we emulate the instructions by hand. Finally, 4) VM86_PROTECTED
 * when we transitioned to protected mode and we should abandon the
 * emulator. No instructions are emulated when in VM86_PROTECTED mode.
 */
void
set_mode(struct regs *regs, enum vm86_mode newmode)
{
	switch (newmode) {
	case VM86_REAL:
		if ((mode == VM86_PROTECTED_TO_REAL) ||
		    (mode == VM86_REAL_TO_PROTECTED)) {
			regs->eflags &= ~EFLAGS_TF;
			real_mode(regs);
		} else if (mode != VM86_REAL)
			panic("unexpected real mode transition");
		break;

	case VM86_REAL_TO_PROTECTED:
		if (mode == VM86_REAL) {
			regs->eflags |= EFLAGS_TF;
			saved_rm_regs.vds = regs->vds;
			saved_rm_regs.ves = regs->ves;
			saved_rm_regs.vfs = regs->vfs;
			saved_rm_regs.vgs = regs->vgs;
			saved_rm_regs.uss = regs->uss;
			oldctx.ds_sel = 0;
			oldctx.es_sel = 0;
			oldctx.fs_sel = 0;
			oldctx.gs_sel = 0;
			oldctx.ss_sel = 0;
		} else if (mode != VM86_REAL_TO_PROTECTED)
			panic("unexpected real-to-protected mode transition");
		break;

	case VM86_PROTECTED_TO_REAL:
		if (mode != VM86_PROTECTED)
			panic("unexpected protected-to-real mode transition");
		break;

	case VM86_PROTECTED:
		if (mode != VM86_REAL_TO_PROTECTED)
			panic("unexpected protected mode transition");
		protected_mode(regs);
		break;
	}

	mode = newmode;
	TRACE((regs, 0, states[mode]));
}

static void
jmpl(struct regs *regs, int prefix)
{
	unsigned n = regs->eip;
	unsigned cs, eip;

	eip = (prefix & DATA32) ? fetch32(regs) : fetch16(regs);
	cs = fetch16(regs);

	TRACE((regs, (regs->eip - n) + 1, "jmpl 0x%x:0x%x", cs, eip));

	regs->cs = cs;
	regs->eip = eip;

	if (mode == VM86_REAL_TO_PROTECTED)		/* jump to protected mode */
		set_mode(regs, VM86_PROTECTED);
	else if (mode == VM86_PROTECTED_TO_REAL)/* jump to real mode */
		set_mode(regs, VM86_REAL);
	else
		panic("jmpl");
}

static void
jmpl_indirect(struct regs *regs, int prefix, unsigned modrm)
{
	unsigned n = regs->eip;
	unsigned cs, eip;
	unsigned addr;

	addr = operand(prefix, regs, modrm);

	eip = (prefix & DATA32) ? read32(addr) : read16(addr);
	addr += (prefix & DATA32) ? 4 : 2;
	cs = read16(addr);

	TRACE((regs, (regs->eip - n) + 1, "jmpl 0x%x:0x%x", cs, eip));

	regs->cs = cs;
	regs->eip = eip;

	if (mode == VM86_REAL_TO_PROTECTED)		/* jump to protected mode */
		set_mode(regs, VM86_PROTECTED);
	else if (mode == VM86_PROTECTED_TO_REAL)/* jump to real mode */
		set_mode(regs, VM86_REAL);
	else
		panic("jmpl");
}

static void
retl(struct regs *regs, int prefix)
{
	unsigned cs, eip;

	if (prefix & DATA32) {
		eip = pop32(regs);
		cs = MASK16(pop32(regs));
	} else {
		eip = pop16(regs);
		cs = pop16(regs);
	}

	TRACE((regs, 1, "retl (to 0x%x:0x%x)", cs, eip));

	regs->cs = cs;
	regs->eip = eip;

	if (mode == VM86_REAL_TO_PROTECTED)		/* jump to protected mode */
		set_mode(regs, VM86_PROTECTED);
	else if (mode == VM86_PROTECTED_TO_REAL)/* jump to real mode */
		set_mode(regs, VM86_REAL);
	else
		panic("retl");
}

static void
interrupt(struct regs *regs, int n)
{
	TRACE((regs, 0, "external interrupt %d", n));
	push16(regs, regs->eflags);
	push16(regs, regs->cs);
	push16(regs, regs->eip);
	regs->eflags &= ~EFLAGS_IF;
	regs->eip = read16(address(regs, 0, n * 4));
	regs->cs = read16(address(regs, 0, n * 4 + 2));
}

/*
 * Most port I/O operations are passed unmodified. We do have to be
 * careful and make sure the emulated program isn't remapping the
 * interrupt vectors. The following simple state machine catches
 * these attempts and rewrites them.
 */
static int
outbyte(struct regs *regs, unsigned prefix, unsigned opc)
{
	static char icw2[2] = { 0 };
	int al, port;

	switch (opc) {
	case 0xE6: /* outb port, al */
		port = fetch8(regs);
		break;
	case 0xEE: /* outb (%dx), al */
		port = MASK16(regs->edx);
		break;
	default:
		return 0;
	}

	al = regs->eax & 0xFF;

	switch (port) {
	case PIC_MASTER + PIC_CMD:
		if (al & (1 << 4)) /* A0=0,D4=1 -> ICW1 */
			icw2[0] = 1;
		break;
	case PIC_MASTER + PIC_IMR:
		if (icw2[0]) {
			icw2[0] = 0;
			printf("Remapping master: ICW2 0x%x -> 0x%x\n",
				al, NR_EXCEPTION_HANDLER);
			al = NR_EXCEPTION_HANDLER;
		}
		break;

	case PIC_SLAVE  + PIC_CMD:
		if (al & (1 << 4)) /* A0=0,D4=1 -> ICW1 */
			icw2[1] = 1;
		break;
	case PIC_SLAVE  + PIC_IMR:
		if (icw2[1]) {
			icw2[1] = 0;
			printf("Remapping slave: ICW2 0x%x -> 0x%x\n",
				al, NR_EXCEPTION_HANDLER+8);
			al = NR_EXCEPTION_HANDLER+8;
		}
		break;
	}

	outb(port, al);
	return 1;
}

static int
inbyte(struct regs *regs, unsigned prefix, unsigned opc)
{
	int port;

	switch (opc) {
	case 0xE4: /* inb al, port */
		port = fetch8(regs);
		break;
	case 0xEC: /* inb al, (%dx) */
		port = MASK16(regs->edx);
		break;
	default:
		return 0;
	}

	regs->eax = (regs->eax & ~0xFF) | inb(port);
	return 1;
}

static void
pushrm(struct regs *regs, int prefix, unsigned modrm)
{
	unsigned n = regs->eip;
	unsigned addr;
	unsigned data;

	addr = operand(prefix, regs, modrm);

	if (prefix & DATA32) {
		data = read32(addr);
		push32(regs, data);
	} else {
		data = read16(addr);
		push16(regs, data);
	}

	TRACE((regs, (regs->eip - n) + 1, "push *0x%x", addr));
}

enum { OPC_INVALID, OPC_EMULATED };

#define rdmsr(msr,val1,val2)				\
	__asm__ __volatile__(				\
		"rdmsr"					\
		: "=a" (val1), "=d" (val2)		\
		: "c" (msr))

#define wrmsr(msr,val1,val2)				\
	__asm__ __volatile__(				\
		"wrmsr"					\
		: /* no outputs */			\
		: "c" (msr), "a" (val1), "d" (val2))

/*
 * Emulate a single instruction, including all its prefixes. We only implement
 * a small subset of the opcodes, and not all opcodes are implemented for each
 * of the four modes we can operate in.
 */
static int
opcode(struct regs *regs)
{
	unsigned eip = regs->eip;
	unsigned opc, modrm, disp;
	unsigned prefix = 0;

	for (;;) {
		switch ((opc = fetch8(regs))) {
		case 0x07: /* pop %es */
			regs->ves = (prefix & DATA32) ?
				pop32(regs) : pop16(regs);
			TRACE((regs, regs->eip - eip, "pop %%es"));
			if (mode == VM86_REAL_TO_PROTECTED) {
				saved_rm_regs.ves = 0;
				oldctx.es_sel = regs->ves;
			}
			return OPC_EMULATED;

		case 0x0F: /* two byte opcode */
			if (mode == VM86_PROTECTED)
				goto invalid;
			switch ((opc = fetch8(regs))) {
			case 0x01:
				switch (((modrm = fetch8(regs)) >> 3) & 7) {
				case 0: /* sgdt */
				case 1: /* sidt */
					goto invalid;
				case 2: /* lgdt */
					if (!lgdt(regs, prefix, modrm))
						goto invalid;
					return OPC_EMULATED;
				case 3: /* lidt */
					if (!lidt(regs, prefix, modrm))
						goto invalid;
					return OPC_EMULATED;
				case 4: /* smsw */
					goto invalid;
				case 5:
					goto invalid;
				case 6: /* lmsw */
					if (!lmsw(regs, prefix, modrm))
						goto invalid;
					return OPC_EMULATED;
				case 7: /* invlpg */
					goto invalid;
				}
				break;
			case 0x09: /* wbinvd */
				return OPC_EMULATED;
			case 0x20: /* mov Rd, Cd (1h) */
			case 0x22:
				if (!movcr(regs, prefix, opc))
					goto invalid;
				return OPC_EMULATED;
			case 0x30: /* WRMSR */
				wrmsr(regs->ecx, regs->eax, regs->edx);
				return OPC_EMULATED;
			case 0x32: /* RDMSR */
				rdmsr(regs->ecx, regs->eax, regs->edx);
				return OPC_EMULATED;
			default:
				goto invalid;
			}
			goto invalid;

		case 0x1F: /* pop %ds */
			regs->vds = (prefix & DATA32) ?
				pop32(regs) : pop16(regs);
			TRACE((regs, regs->eip - eip, "pop %%ds"));
			if (mode == VM86_REAL_TO_PROTECTED) {
				saved_rm_regs.vds = 0;
				oldctx.ds_sel = regs->vds;
			}
			return OPC_EMULATED;

		case 0x26:
			TRACE((regs, regs->eip - eip, "%%es:"));
			prefix |= SEG_ES;
			continue;

		case 0x2E:
			TRACE((regs, regs->eip - eip, "%%cs:"));
			prefix |= SEG_CS;
			continue;

		case 0x36:
			TRACE((regs, regs->eip - eip, "%%ss:"));
			prefix |= SEG_SS;
			continue;

		case 0x39: /* addr32 cmp r16, r/m16 */
		case 0x3B: /* addr32 cmp r/m16, r16 */
			if (mode != VM86_REAL && mode != VM86_REAL_TO_PROTECTED)
				goto invalid;
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!cmp(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0x3E:
			TRACE((regs, regs->eip - eip, "%%ds:"));
			prefix |= SEG_DS;
			continue;

		case 0x64:
			TRACE((regs, regs->eip - eip, "%%fs:"));
			prefix |= SEG_FS;
			continue;

		case 0x65:
			TRACE((regs, regs->eip - eip, "%%gs:"));
			prefix |= SEG_GS;
			continue;

		case 0x66:
			TRACE((regs, regs->eip - eip, "data32"));
			prefix |= DATA32;
			continue;

		case 0x67:
			TRACE((regs, regs->eip - eip, "addr32"));
			prefix |= ADDR32;
			continue;

		case 0x88: /* addr32 mov r8, r/m8 */
		case 0x8A: /* addr32 mov r/m8, r8 */
			if (mode != VM86_REAL && mode != VM86_REAL_TO_PROTECTED)
				goto invalid;
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!movr(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0x89: /* addr32 mov r16, r/m16 */
			if (mode == VM86_PROTECTED_TO_REAL) {
				unsigned modrm = fetch8(regs);
				unsigned addr = operand(prefix, regs, modrm);
				unsigned val, r = (modrm >> 3) & 7;

				if (prefix & DATA32) {
					val = getreg16(regs, r);
					write32(addr, val);
				} else {
					val = getreg32(regs, r);
					write16(addr, MASK16(val));
				}
				TRACE((regs, regs->eip - eip,
					"mov %%%s, *0x%x", rnames[r], addr));
				return OPC_EMULATED;
			}
		case 0x8B: /* addr32 mov r/m16, r16 */
			if (mode != VM86_REAL && mode != VM86_REAL_TO_PROTECTED)
				goto invalid;
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!movr(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0x8E: /* mov r16, sreg */
			if (!mov_to_seg(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0x8F: /* addr32 pop r/m16 */
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!pop(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0x90: /* nop */
			TRACE((regs, regs->eip - eip, "nop"));
			return OPC_EMULATED;

		case 0x9C: /* pushf */
			TRACE((regs, regs->eip - eip, "pushf"));
			if (prefix & DATA32)
				push32(regs, regs->eflags & ~EFLAGS_VM);
			else
				push16(regs, regs->eflags & ~EFLAGS_VM);
			return OPC_EMULATED;

		case 0x9D: /* popf */
			TRACE((regs, regs->eip - eip, "popf"));
			if (prefix & DATA32)
				regs->eflags = pop32(regs);
			else
				regs->eflags = (regs->eflags & 0xFFFF0000L) |
								pop16(regs);
			regs->eflags |= EFLAGS_VM;
			return OPC_EMULATED;

		case 0xA1: /* mov ax, r/m16 */
			{
				int addr, data;
				int seg = segment(prefix, regs, regs->vds);
				int offset = prefix & ADDR32? fetch32(regs) : fetch16(regs);

				if (prefix & DATA32) {
					addr = address(regs, seg, offset);
					data = read32(addr);
					setreg32(regs, 0, data);
				} else {
					addr = address(regs, seg, offset);
					data = read16(addr);
					setreg16(regs, 0, data);
				}
				TRACE((regs, regs->eip - eip, "mov *0x%x, %%ax", addr));
			}
			return OPC_EMULATED;

		case 0xBB: /* mov bx, imm16 */
			{
				int data;
				if (prefix & DATA32) {
					data = fetch32(regs);
					setreg32(regs, 3, data);
				} else {
					data = fetch16(regs);
					setreg16(regs, 3, data);
				}
				TRACE((regs, regs->eip - eip, "mov $0x%x, %%bx", data));
			}
			return OPC_EMULATED;

		case 0xC6: /* addr32 movb $imm, r/m8 */
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!movr(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xCB: /* retl */
			if ((mode == VM86_REAL_TO_PROTECTED) ||
				(mode == VM86_PROTECTED_TO_REAL)) {
				retl(regs, prefix);
				return OPC_INVALID;
			}
			goto invalid;

		case 0xCD: /* int $n */
			TRACE((regs, regs->eip - eip, "int"));
			interrupt(regs, fetch8(regs));
			return OPC_EMULATED;

		case 0xCF: /* iret */
			if (prefix & DATA32) {
				TRACE((regs, regs->eip - eip, "data32 iretd"));
				regs->eip = pop32(regs);
				regs->cs = pop32(regs);
				regs->eflags = pop32(regs);
			} else {
				TRACE((regs, regs->eip - eip, "iret"));
				regs->eip = pop16(regs);
				regs->cs = pop16(regs);
				regs->eflags = (regs->eflags & 0xFFFF0000L) |
								pop16(regs);
			}
			return OPC_EMULATED;

		case 0xE4: /* inb al, port */
			if (!inbyte(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xE6: /* outb port, al */
			if (!outbyte(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xEA: /* jmpl */
			if ((mode == VM86_REAL_TO_PROTECTED) ||
				(mode == VM86_PROTECTED_TO_REAL)) {
				jmpl(regs, prefix);
				return OPC_INVALID;
			}
			goto invalid;

		case 0xFF: /* jmpl (indirect) */
			{
				unsigned modrm = fetch8(regs);
				switch((modrm >> 3) & 7) {
				case 5: /* jmpl (indirect) */
					if ((mode == VM86_REAL_TO_PROTECTED) ||
						(mode == VM86_PROTECTED_TO_REAL)) {
						jmpl_indirect(regs, prefix, modrm);
						return OPC_INVALID;
					}
					goto invalid;

				case 6: /* push r/m16 */
					pushrm(regs, prefix, modrm);
					return OPC_EMULATED;

				default:
					goto invalid;
				}
			}

		case 0xEB: /* short jump */
			if ((mode == VM86_REAL_TO_PROTECTED) ||
				(mode == VM86_PROTECTED_TO_REAL)) {
				disp = (char) fetch8(regs);
				TRACE((regs, 2, "jmp 0x%x", regs->eip + disp));
				regs->eip += disp;
				return OPC_EMULATED;
			}
			goto invalid;

		case 0xEC: /* inb al, (%dx) */
			if (!inbyte(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xEE: /* outb (%dx), al */
			if (!outbyte(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xF0: /* lock */
			TRACE((regs, regs->eip - eip, "lock"));
			continue;

		case 0xF6: /* addr32 testb $imm, r/m8 */
			if ((prefix & ADDR32) == 0)
				goto invalid;
			if (!test(regs, prefix, opc))
				goto invalid;
			return OPC_EMULATED;

		case 0xFA: /* cli */
			TRACE((regs, regs->eip - eip, "cli"));
			regs->eflags &= ~EFLAGS_IF;
			return OPC_EMULATED;

		case 0xFB: /* sti */
			TRACE((regs, regs->eip - eip, "sti"));
			regs->eflags |= EFLAGS_IF;
			return OPC_EMULATED;

		default:
			goto invalid;
		}
	}

invalid:
	regs->eip = eip;
	TRACE((regs, regs->eip - eip, "opc 0x%x", opc));
	return OPC_INVALID;
}

void
emulate(struct regs *regs)
{
	unsigned flteip;
	int nemul = 0;

	/* emulate as many instructions as possible */
	while (opcode(regs) != OPC_INVALID)
		nemul++;

	/* detect the case where we are not making progress */
	if (nemul == 0 && prev_eip == regs->eip) {
		flteip = address(regs, MASK16(regs->cs), regs->eip);
		panic("Unknown opcode at %04x:%04x=0x%x",
			MASK16(regs->cs), regs->eip, flteip);
	} else
		prev_eip = regs->eip;
}

void
trap(int trapno, int errno, struct regs *regs)
{
	/* emulate device interrupts */
	if (trapno >= NR_EXCEPTION_HANDLER) {
		int irq = trapno - NR_EXCEPTION_HANDLER;
		if (irq < 8) 
			interrupt(regs, irq + 8);
		else
			interrupt(regs, 0x70 + (irq - 8));
		return;
	}

	switch (trapno) {
	case 1: /* Debug */
		if (regs->eflags & EFLAGS_VM) {
			/* emulate any 8086 instructions  */
			if (mode == VM86_REAL)
				return;
			if (mode != VM86_REAL_TO_PROTECTED)
				panic("not in real-to-protected mode");
			emulate(regs);
			return;
		}
		goto invalid;

	case 13: /* GPF */
		if (regs->eflags & EFLAGS_VM) {
			/* emulate any 8086 instructions  */
			if (mode == VM86_PROTECTED)
				panic("unexpected protected mode");
			emulate(regs);
			return;
		}
		goto invalid;

	default:
	invalid:
		printf("Trap (0x%x) while in %s mode\n",
			trapno, regs->eflags & EFLAGS_VM ? "real" : "protected");
		if (trapno == 14)
			printf("Page fault address 0x%x\n", get_cr2());
		dump_regs(regs);
		halt();
	}
}
