/*
Linux Real Mode Interface - A library of DPMI-like functions for Linux.

Copyright (C) 1998 by Josh Vanderhoof

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL JOSH VANDERHOOF BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

#if defined(__i386__) && (defined(__linux__) || defined(__NetBSD__) \
	|| defined(__FreeBSD__) || defined(__OpenBSD__))

#include <stdio.h>
#include <string.h>

#if defined(__linux__)

#include <asm/vm86.h>
#include <signal.h>

#ifdef USE_LIBC_VM86
#include <sys/vm86.h>
#endif

#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)

#include <sys/param.h>
#include <signal.h>
#include <setjmp.h>
#include <machine/psl.h>
#include <machine/vm86.h>
#include <machine/sysarch.h>

#endif /* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>

#define LRMI_C

#include "lrmi.h"
#include "libvga.h"

#define REAL_MEM_BASE 	((void *)0x10000)
#define REAL_MEM_SIZE 	0x40000
#define REAL_MEM_BLOCKS 	0x100

struct mem_block {
	unsigned int size : 20;
	unsigned int free : 1;
};

static struct {
	int ready;
	int count;
	struct mem_block blocks[REAL_MEM_BLOCKS];
} mem_info = { 0 };

static int
real_mem_init(void)
{
	void *m;
	int fd_zero;

	if (mem_info.ready)
		return 1;

	fd_zero = open("/dev/zero", O_RDONLY);
	if (fd_zero == -1) {
		perror("open /dev/zero");
		return 0;
	}

	m = mmap((void *)REAL_MEM_BASE, REAL_MEM_SIZE,
	 PROT_READ | PROT_WRITE | PROT_EXEC,
	 MAP_FIXED | MAP_PRIVATE, fd_zero, 0);

	if (m == (void *)-1) {
		perror("mmap /dev/zero");
		close(fd_zero);
		return 0;
	}

	close(fd_zero);

	mem_info.ready = 1;
	mem_info.count = 1;
	mem_info.blocks[0].size = REAL_MEM_SIZE;
	mem_info.blocks[0].free = 1;

	return 1;
}

static void
real_mem_deinit(void)
{
	if (mem_info.ready) {
		munmap((void *)REAL_MEM_BASE, REAL_MEM_SIZE);
		mem_info.ready = 0;
	}
}


static void
insert_block(int i)
{
	memmove(
	 mem_info.blocks + i + 1,
	 mem_info.blocks + i,
	 (mem_info.count - i) * sizeof(struct mem_block));

	mem_info.count++;
}

static void
delete_block(int i)
{
	mem_info.count--;

	memmove(
	 mem_info.blocks + i,
	 mem_info.blocks + i + 1,
	 (mem_info.count - i) * sizeof(struct mem_block));
}

static void *
LRMI_alloc_real(int size)
{
	int i;
	char *r = (char *)REAL_MEM_BASE;

	if (!mem_info.ready)
		return NULL;

	if (mem_info.count == REAL_MEM_BLOCKS)
		return NULL;

	size = (size + 15) & ~15;

	for (i = 0; i < mem_info.count; i++) {
		if (mem_info.blocks[i].free && size < mem_info.blocks[i].size) {
			insert_block(i);

			mem_info.blocks[i].size = size;
			mem_info.blocks[i].free = 0;
			mem_info.blocks[i + 1].size -= size;

			return (void *)r;
		}

		r += mem_info.blocks[i].size;
	}

	return NULL;
}


static void
LRMI_free_real(void *m)
{
	int i;
	char *r = (char *)REAL_MEM_BASE;

	if (!mem_info.ready)
		return;

	i = 0;
	while (m != (void *)r) {
		r += mem_info.blocks[i].size;
		i++;
		if (i == mem_info.count)
			return;
	}

	mem_info.blocks[i].free = 1;

	if (i + 1 < mem_info.count && mem_info.blocks[i + 1].free) {
		mem_info.blocks[i].size += mem_info.blocks[i + 1].size;
		delete_block(i + 1);
	}

	if (i - 1 >= 0 && mem_info.blocks[i - 1].free) {
		mem_info.blocks[i - 1].size += mem_info.blocks[i].size;
		delete_block(i);
	}
}


#if defined(__linux__)
#define DEFAULT_VM86_FLAGS 	(IF_MASK | IOPL_MASK)
#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#define DEFAULT_VM86_FLAGS  (PSL_I | PSL_IOPL)
#define TF_MASK         PSL_T
#define VIF_MASK        PSL_VIF
#endif
#define DEFAULT_STACK_SIZE 	0x1000
#define RETURN_TO_32_INT 	255

#if defined(__linux__)
#define CONTEXT_REGS	context.vm.regs
#define REG(x)			x
#elif defined(__NetBSD__) || defined(__OpenBSD__)
#define CONTEXT_REGS	context.vm.substr.regs
#define REG(x)			vmsc.sc_ ## x
#elif defined(__FreeBSD__)
#define CONTEXT_REGS	context.vm.uc
#define REG(x)			uc_mcontext.mc_ ## x
#endif

static struct {
	int ready;
	unsigned short ret_seg, ret_off;
	unsigned short stack_seg, stack_off;
#if defined(__linux__) || defined(__NetBSD__) || defined(__OpenBSD__)
	struct vm86_struct vm;
#elif defined(__FreeBSD__)
	struct {
		struct vm86_init_args init;
		ucontext_t uc;
	} vm;
#endif
#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
	int success;
	jmp_buf env;
	void *old_sighandler;
	int vret;
#endif
} context = { 0 };


static inline void
set_bit(unsigned int bit, void *array)
{
	unsigned char *a = array;

	a[bit / 8] |= (1 << (bit % 8));
}


static inline unsigned int
get_int_seg(int i)
{
	return *(unsigned short *)(i * 4 + 2);
}


static inline unsigned int
get_int_off(int i)
{
	return *(unsigned short *)(i * 4);
}


static inline void
pushw(unsigned short i)
{
	CONTEXT_REGS.REG(esp) -= 2;
	*(unsigned short *)(((unsigned int)CONTEXT_REGS.REG(ss) << 4) +
		CONTEXT_REGS.REG(esp)) = i;
}


static int
LRMI_init(void)
{
	void *m;
	int fd_mem;

	if (context.ready)
		return 1;

	if (!real_mem_init())
		return 0;

	/*
	 Map the Interrupt Vectors (0x0 - 0x400) + BIOS data (0x400 - 0x502)
	 and the ROM (0xa0000 - 0x100000)
	*/
	fd_mem = __svgalib_mem_fd;

	if (fd_mem == -1) {
		real_mem_deinit();
		perror("open /dev/svga");
		return 0;
	}

	m = mmap((void *)0, 0x502,
	 PROT_READ | PROT_WRITE | PROT_EXEC,
	 MAP_FIXED | MAP_PRIVATE, fd_mem, 0);

	if (m == (void *)-1) {
		real_mem_deinit();
		perror("mmap /dev/svga");
		return 0;
	}

	m = mmap((void *)0xa0000, 0x100000 - 0xa0000,
	 PROT_READ | PROT_WRITE,
	 MAP_FIXED | MAP_SHARED, fd_mem, 0xa0000);

	if (m == (void *)-1) {
		munmap((void *)0, 0x502);
		real_mem_deinit();
		perror("mmap /dev/mem");
		return 0;
	}

	/*
	 Allocate a stack
	*/
	m = LRMI_alloc_real(DEFAULT_STACK_SIZE);

	context.stack_seg = (unsigned int)m >> 4;
	context.stack_off = DEFAULT_STACK_SIZE;

	/*
	 Allocate the return to 32 bit routine
	*/
	m = LRMI_alloc_real(2);

	context.ret_seg = (unsigned int)m >> 4;
	context.ret_off = (unsigned int)m & 0xf;

	((unsigned char *)m)[0] = 0xcd; 	/* int opcode */
	((unsigned char *)m)[1] = RETURN_TO_32_INT;

	memset(&context.vm, 0, sizeof(context.vm));

	/*
	 Enable kernel emulation of all ints except RETURN_TO_32_INT
	*/
#if defined(__linux__)
	memset(&context.vm.int_revectored, 0, sizeof(context.vm.int_revectored));
	set_bit(RETURN_TO_32_INT, &context.vm.int_revectored);
#elif defined(__NetBSD__) || defined(__OpenBSD__)
	set_bit(RETURN_TO_32_INT, &context.vm.int_byuser);
#elif defined(__FreeBSD__)
	set_bit(RETURN_TO_32_INT, &context.vm.init.int_map);
#endif

	context.ready = 1;

	return 1;
}


static void
set_regs(struct LRMI_regs *r)
{
	CONTEXT_REGS.REG(edi) = r->edi;
	CONTEXT_REGS.REG(esi) = r->esi;
	CONTEXT_REGS.REG(ebp) = r->ebp;
	CONTEXT_REGS.REG(ebx) = r->ebx;
	CONTEXT_REGS.REG(edx) = r->edx;
	CONTEXT_REGS.REG(ecx) = r->ecx;
	CONTEXT_REGS.REG(eax) = r->eax;
	CONTEXT_REGS.REG(eflags) = DEFAULT_VM86_FLAGS;
	CONTEXT_REGS.REG(es) = r->es;
	CONTEXT_REGS.REG(ds) = r->ds;
	CONTEXT_REGS.REG(fs) = r->fs;
	CONTEXT_REGS.REG(gs) = r->gs;
}


static void
get_regs(struct LRMI_regs *r)
{
	r->edi = CONTEXT_REGS.REG(edi);
	r->esi = CONTEXT_REGS.REG(esi);
	r->ebp = CONTEXT_REGS.REG(ebp);
	r->ebx = CONTEXT_REGS.REG(ebx);
	r->edx = CONTEXT_REGS.REG(edx);
	r->ecx = CONTEXT_REGS.REG(ecx);
	r->eax = CONTEXT_REGS.REG(eax);
	r->flags = CONTEXT_REGS.REG(eflags);
	r->es = CONTEXT_REGS.REG(es);
	r->ds = CONTEXT_REGS.REG(ds);
	r->fs = CONTEXT_REGS.REG(fs);
	r->gs = CONTEXT_REGS.REG(gs);
}

#define DIRECTION_FLAG 	(1 << 10)

enum {
	CSEG = 0x2e, SSEG = 0x36, DSEG = 0x3e,
	ESEG = 0x26, FSEG = 0x64, GSEG = 0x65,
};

static void
em_ins(int size)
{
	unsigned int edx, edi;

	edx = CONTEXT_REGS.REG(edx) & 0xffff;
	edi = CONTEXT_REGS.REG(edi) & 0xffff;
	edi += (unsigned int)CONTEXT_REGS.REG(es) << 4;

	if (CONTEXT_REGS.REG(eflags) & 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)CONTEXT_REGS.REG(es) << 4;

	CONTEXT_REGS.REG(edi) &= 0xffff0000;
	CONTEXT_REGS.REG(edi) |= edi & 0xffff;
}

static void
em_rep_ins(int size)
{
	unsigned int cx;

	cx = CONTEXT_REGS.REG(ecx) & 0xffff;

	while (cx--)
		em_ins(size);

	CONTEXT_REGS.REG(ecx) &= 0xffff0000;
}

static void
em_outs(int size, int seg)
{
	unsigned int edx, esi, base;

	edx = CONTEXT_REGS.REG(edx) & 0xffff;
	esi = CONTEXT_REGS.REG(esi) & 0xffff;

	switch (seg) {
	case CSEG: base = CONTEXT_REGS.REG(cs); break;
	case SSEG: base = CONTEXT_REGS.REG(ss); break;
	case ESEG: base = CONTEXT_REGS.REG(es); break;
	case FSEG: base = CONTEXT_REGS.REG(fs); break;
	case GSEG: base = CONTEXT_REGS.REG(gs); break;
	default:
	case DSEG: base = CONTEXT_REGS.REG(ds); break;
	}

	esi += base << 4;

	if (CONTEXT_REGS.REG(eflags) & 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;

	CONTEXT_REGS.REG(esi) &= 0xffff0000;
	CONTEXT_REGS.REG(esi) |= esi & 0xffff;
}

static void
em_rep_outs(int size, int seg)
{
	unsigned int cx;

	cx = CONTEXT_REGS.REG(ecx) & 0xffff;

	while (cx--)
		em_outs(size, seg);

	CONTEXT_REGS.REG(ecx) &= 0xffff0000;
}

static void
em_inb(int port)
{
	context.vm.regs.eax&=0xffffff00;
	context.vm.regs.eax|=port_in(port);
}

static void
em_inw(int port)
{
	context.vm.regs.eax&=0xffff0000;
	context.vm.regs.eax|=port_inw(port);
}

static void
em_inl(int port)
{
	context.vm.regs.eax=port_inl(port);
}

static void
em_outb(int port)
{
	port_out(context.vm.regs.eax,port);
}

static void
em_outw(int port)
{
	port_outw(context.vm.regs.eax,port);
}

static void
em_outl(int port)
{
	port_outl(context.vm.regs.eax,port);
}

static int
emulate(void)
{
	unsigned char *insn;
	struct {
		unsigned char seg;
		unsigned int size : 1;
		unsigned int rep : 1;
	} prefix = { DSEG, 0, 0 };
	int i = 0;

	insn = (unsigned char *)((unsigned int)CONTEXT_REGS.REG(cs) << 4);
	insn += CONTEXT_REGS.REG(eip);

	while (1) {
		if (insn[i] == 0x66) {
			prefix.size = 1 - prefix.size;
			i++;
		} else if (insn[i] == 0xf3) {
			prefix.rep = 1;
			i++;
		} else if (insn[i] == CSEG || insn[i] == SSEG
		 || insn[i] == DSEG || insn[i] == ESEG
		 || insn[i] == FSEG || insn[i] == GSEG) {
			prefix.seg = insn[i];
			i++;
		} else if (insn[i] == 0xf0 || insn[i] == 0xf2
		 || insn[i] == 0x67) {
			/* these prefixes are just ignored */
			i++;
		} else if (insn[i] == 0x6c) {
			if (prefix.rep)
				em_rep_ins(1);
			else
				em_ins(1);
			i++;
			break;
		} else if (insn[i] == 0x6d) {
			if (prefix.rep) {
				if (prefix.size)
					em_rep_ins(4);
				else
					em_rep_ins(2);
			} else {
				if (prefix.size)
					em_ins(4);
				else
					em_ins(2);
			}
			i++;
			break;
		} else if (insn[i] == 0x6e) {
			if (prefix.rep)
				em_rep_outs(1, prefix.seg);
			else
				em_outs(1, prefix.seg);
			i++;
			break;
		} else if (insn[i] == 0x6f) {
			if (prefix.rep) {
				if (prefix.size)
					em_rep_outs(4, prefix.seg);
				else
					em_rep_outs(2, prefix.seg);
			} else {
				if (prefix.size)
					em_outs(4, prefix.seg);
				else
					em_outs(2, prefix.seg);
			}
			i++;
			break;
		} else if (insn[i] == 0xe4) {
			em_inb(insn[i + 1]);
			i += 2;
			break;
		} else if (insn[i] == 0xe5) {
			i++;
			if (prefix.size)
				em_inl(insn[i]);
			else
				em_inw(insn[i]);
			i++;
			break;
		} else if (insn[i] == 0xe6)	{
			i++;
			em_outb(insn[i]);
			i++;
			break;
		} else if (insn[i] == 0xe7) {
			i++;
			if (prefix.size)
				em_outl(insn[i]);
			else
				em_outw(insn[i]);
			i++;
			break;
		} else if (insn[i] == 0xec) {
			em_inb(CONTEXT_REGS.REG(edx)&0xffff);
			i++;
			break;
		} else if (insn[i] == 0xed) {
			if (prefix.size)
				em_inl(CONTEXT_REGS.REG(edx)&0xffff);
			else
				em_inw(CONTEXT_REGS.REG(edx)&0xffff);
			i++;
			break;
		} else if (insn[i] == 0xee) {
			em_outb(CONTEXT_REGS.REG(edx)&0xffff);
			i++;
			break;
		} else if (insn[i] == 0xef) {
			if (prefix.size)
				em_outl(CONTEXT_REGS.REG(edx)&0xffff);
			else
				em_outw(CONTEXT_REGS.REG(edx)&0xffff);

			i++;
			break;
		} else
			return 0;
	}

	CONTEXT_REGS.REG(eip) += i;
	return 1;
}


#if defined(__linux__)
/*
 I don't know how to make sure I get the right vm86() from libc.
 The one I want is syscall # 113 (vm86old() in libc 5, vm86() in glibc)
 which should be declared as "int vm86(struct vm86_struct *);" in
 <sys/vm86.h>.

 This just does syscall 113 with inline asm, which should work
 for both libc's (I hope).
*/
#if !defined(USE_LIBC_VM86)
static int
lrmi_vm86(struct vm86_struct *vm)
{
	int r;
#ifdef __PIC__
	asm volatile (
	 "pushl %%ebx\n\t"
	 "movl %2, %%ebx\n\t"
	 "int $0x80\n\t"
	 "popl %%ebx"
	 : "=a" (r)
	 : "0" (113), "r" (vm));
#else
	asm volatile (
	 "int $0x80"
	 : "=a" (r)
	 : "0" (113), "b" (vm));
#endif
	return r;
}
#else
#define lrmi_vm86 vm86
#endif
#endif /* __linux__ */


static void
debug_info(int vret)
{
#ifdef LRMI_DEBUG
	int i;
	unsigned char *p;

	fputs("vm86() failed\n", stderr);
	fprintf(stderr, "return = 0x%x\n", vret);
	fprintf(stderr, "eax = 0x%08x\n", CONTEXT_REGS.REG(eax));
	fprintf(stderr, "ebx = 0x%08x\n", CONTEXT_REGS.REG(ebx));
	fprintf(stderr, "ecx = 0x%08x\n", CONTEXT_REGS.REG(ecx));
	fprintf(stderr, "edx = 0x%08x\n", CONTEXT_REGS.REG(edx));
	fprintf(stderr, "esi = 0x%08x\n", CONTEXT_REGS.REG(esi));
	fprintf(stderr, "edi = 0x%08x\n", CONTEXT_REGS.REG(edi));
	fprintf(stderr, "ebp = 0x%08x\n", CONTEXT_REGS.REG(ebp));
	fprintf(stderr, "eip = 0x%08x\n", CONTEXT_REGS.REG(eip));
	fprintf(stderr, "cs  = 0x%04x\n", CONTEXT_REGS.REG(cs));
	fprintf(stderr, "esp = 0x%08x\n", CONTEXT_REGS.REG(esp));
	fprintf(stderr, "ss  = 0x%04x\n", CONTEXT_REGS.REG(ss));
	fprintf(stderr, "ds  = 0x%04x\n", CONTEXT_REGS.REG(ds));
	fprintf(stderr, "es  = 0x%04x\n", CONTEXT_REGS.REG(es));
	fprintf(stderr, "fs  = 0x%04x\n", CONTEXT_REGS.REG(fs));
	fprintf(stderr, "gs  = 0x%04x\n", CONTEXT_REGS.REG(gs));
	fprintf(stderr, "eflags  = 0x%08x\n", CONTEXT_REGS.REG(eflags));

	fputs("cs:ip = [ ", stderr);

	p = (unsigned char *)((CONTEXT_REGS.REG(cs) << 4) + (CONTEXT_REGS.REG(eip) & 0xffff));

	for (i = 0; i < 16; ++i)
		fprintf(stderr, "%02x ", (unsigned int)p[i]);

	fputs("]\n", stderr);
#endif
}


#if defined(__linux__)
static int
run_vm86(void)
{
	unsigned int vret;
	sigset_t all_sigs, old_sigs;
	unsigned long old_gs, old_fs;

	while (1) {
		// FIXME: may apply this to BSD equivalents?
		sigfillset(&all_sigs);
		sigprocmask(SIG_SETMASK, &all_sigs, &old_sigs);
		asm volatile ("movl %%gs, %0" : "=rm" (old_gs));
		asm volatile ("movl %%fs, %0" : "=rm" (old_fs));
		vret = lrmi_vm86(&context.vm);
		asm volatile ("movl %0, %%gs" :: "rm" (old_gs));
		asm volatile ("movl %0, %%fs" :: "rm" (old_fs));
		sigprocmask(SIG_SETMASK, &old_sigs, NULL);

		if (VM86_TYPE(vret) == VM86_INTx) {
			unsigned int v = VM86_ARG(vret);

			if (v == RETURN_TO_32_INT)
				return 1;

			pushw(CONTEXT_REGS.REG(eflags));
			pushw(CONTEXT_REGS.REG(cs));
			pushw(CONTEXT_REGS.REG(eip));

			CONTEXT_REGS.REG(cs) = get_int_seg(v);
			CONTEXT_REGS.REG(eip) = get_int_off(v);
			CONTEXT_REGS.REG(eflags) &= ~(VIF_MASK | TF_MASK);

			continue;
		}

		if (VM86_TYPE(vret) != VM86_UNKNOWN)
			break;

		if (!emulate())
			break;
	}

	debug_info(vret);

	return 0;
}
#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#if defined(__NetBSD__) || defined(__OpenBSD__)
static void
vm86_callback(int sig, int code, struct sigcontext *sc)
{
	/* Sync our context with what the kernel develivered to us. */
	memcpy(&CONTEXT_REGS, sc, sizeof(*sc));

	switch (VM86_TYPE(code)) {
		case VM86_INTx:
		{
			unsigned int v = VM86_ARG(code);

			if (v == RETURN_TO_32_INT) {
				context.success = 1;
				longjmp(context.env, 1);
			}

			pushw(CONTEXT_REGS.REG(eflags));
			pushw(CONTEXT_REGS.REG(cs));
			pushw(CONTEXT_REGS.REG(eip));

			CONTEXT_REGS.REG(cs) = get_int_seg(v);
			CONTEXT_REGS.REG(eip) = get_int_off(v);
			CONTEXT_REGS.REG(eflags) &= ~(VIF_MASK | TF_MASK);

			break;
		}

		case VM86_UNKNOWN:
			if (emulate() == 0) {
				context.success = 0;
				context.vret = code;
				longjmp(context.env, 1);
			}
			break;

		default:
			context.success = 0;
			context.vret = code;
			longjmp(context.env, 1);
			return;
	}

	/* ...and sync our context back to the kernel. */
	memcpy(sc, &CONTEXT_REGS, sizeof(*sc));
}
#elif defined(__FreeBSD__)
static void
vm86_callback(int sig, int code, struct sigcontext *sc)
{
	unsigned char *addr;

	/* Sync our context with what the kernel develivered to us. */
	memcpy(&CONTEXT_REGS, sc, sizeof(*sc));

	if (code) {
		/* XXX probably need to call original signal handler here */
		context.success = 0;
		context.vret = code;
		longjmp(context.env, 1);
	}

	addr = (unsigned char *)((CONTEXT_REGS.REG(cs) << 4) +
		CONTEXT_REGS.REG(eip));

	if (addr[0] == 0xcd) { /* int opcode */
		if (addr[1] == RETURN_TO_32_INT) {
			context.success = 1;
			longjmp(context.env, 1);
		}

		pushw(CONTEXT_REGS.REG(eflags));
		pushw(CONTEXT_REGS.REG(cs));
		pushw(CONTEXT_REGS.REG(eip));

		CONTEXT_REGS.REG(cs) = get_int_seg(addr[1]);
		CONTEXT_REGS.REG(eip) = get_int_off(addr[1]);
		CONTEXT_REGS.REG(eflags) &= ~(VIF_MASK | TF_MASK);
	} else {
		if (emulate() == 0) {
			context.success = 0;
			longjmp(context.env, 1);
		}
	}

	/* ...and sync our context back to the kernel. */
	memcpy(sc, &CONTEXT_REGS, sizeof(*sc));
}
#endif /* __FreeBSD__ */

static int
run_vm86(void)
{
	if (context.old_sighandler) {
#ifdef LRMI_DEBUG
		fprintf(stderr, "run_vm86: callback already installed\n");
#endif
		return (0);
	}

#if defined(__NetBSD__) || defined(__OpenBSD__)
	context.old_sighandler = signal(SIGURG, (void (*)(int))vm86_callback);
#elif defined(__FreeBSD__)
	context.old_sighandler = signal(SIGBUS, (void (*)(int))vm86_callback);
#endif

	if (context.old_sighandler == (void *)-1) {
		context.old_sighandler = NULL;
#ifdef LRMI_DEBUG
		fprintf(stderr, "run_vm86: cannot install callback\n");
#endif
		return (0);
	}

	if (setjmp(context.env)) {
#if defined(__NetBSD__) || defined(__OpenBSD__)
		(void) signal(SIGURG, context.old_sighandler);
#elif defined(__FreeBSD__)
		(void) signal(SIGBUS, context.old_sighandler);
#endif
		context.old_sighandler = NULL;

		if (context.success)
			return (1);
		debug_info(context.vret);
		return (0);
	}

#if defined(__NetBSD__) || defined(__OpenBSD__)
	if (i386_vm86(&context.vm) == -1)
		return (0);
#elif defined(__FreeBSD__)
	if (i386_vm86(VM86_INIT, &context.vm.init))
		return 0;

	CONTEXT_REGS.REG(eflags) |= PSL_VM | PSL_VIF;
	sigreturn(&context.vm.uc);
#endif /* __FreeBSD__ */

	/* NOTREACHED */
	return (0);
}
#endif	/* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */

static int
LRMI_call(struct LRMI_regs *r)
{
	unsigned int vret;

	memset(&CONTEXT_REGS, 0, sizeof(CONTEXT_REGS));

	set_regs(r);

	CONTEXT_REGS.REG(cs) = r->cs;
	CONTEXT_REGS.REG(eip) = r->ip;

	if (r->ss == 0 && r->sp == 0) {
		CONTEXT_REGS.REG(ss) = context.stack_seg;
		CONTEXT_REGS.REG(esp) = context.stack_off;
	} else {
		CONTEXT_REGS.REG(ss) = r->ss;
		CONTEXT_REGS.REG(esp) = r->sp;
	}

	pushw(context.ret_seg);
	pushw(context.ret_off);

	vret = run_vm86();

	get_regs(r);

	return vret;
}


static int
LRMI_int(int i, struct LRMI_regs *r)
{
	unsigned int vret;
	unsigned int seg, off;

	seg = get_int_seg(i);
	off = get_int_off(i);

	/*
	 If the interrupt is in regular memory, it's probably
	 still pointing at a dos TSR (which is now gone).
	*/
	if (seg < 0xa000 || (seg << 4) + off >= 0x100000) {
#ifdef LRMI_DEBUG
		fprintf(stderr, "Int 0x%x is not in rom (%04x:%04x)\n", i, seg, off);
#endif
		return 0;
	}

	memset(&CONTEXT_REGS, 0, sizeof(CONTEXT_REGS));

	set_regs(r);

	CONTEXT_REGS.REG(cs) = seg;
	CONTEXT_REGS.REG(eip) = off;

	if (r->ss == 0 && r->sp == 0) {
		CONTEXT_REGS.REG(ss) = context.stack_seg;
		CONTEXT_REGS.REG(esp) = context.stack_off;
	} else {
		CONTEXT_REGS.REG(ss) = r->ss;
		CONTEXT_REGS.REG(esp) = r->sp;
	}

	pushw(DEFAULT_VM86_FLAGS);
	pushw(context.ret_seg);
	pushw(context.ret_off);

	vret = run_vm86();

	get_regs(r);

	return vret;
}

LRMI_callbacks __svgalib_default_LRMI_callbacks = {
	.rm_init = LRMI_init,
	.rm_call = LRMI_call,
	.rm_int = LRMI_int,
	.rm_alloc_real = LRMI_alloc_real,
	.rm_free_real = LRMI_free_real,
};


#else /* (__linux__ || __NetBSD__ || __FreeBSD__ || __OpenBSD__) && __i386__ */
#warning "LRMI is not supported on your system!"
#endif
