// Code specific to x86 hosts running Linux.

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <ucontext.h>
#include <sys/ucontext.h>
#include <asm/ldt.h>
#include <errno.h>

#include "vx32.h"
#include "vx32impl.h"
#include "os.h"

extern int modify_ldt(int, void*, unsigned long);

int vxemu_map(vxemu *emu, vxmmap *mm)
{
	struct vxproc *vxp;
	struct user_desc desc;
	uint ldt[2];
#ifdef __x86_64
	static int didflat;
#endif

	vxp = emu->proc;
	emu->datasel = vxp->vxpno * 16 + 16 + 4 + 3;	// 4=LDT, 3=RPL
	emu->emusel = emu->datasel + 8;

	if (emu->ldt_base != (uintptr_t)mm->base || emu->ldt_size != mm->size) {
		// Set up the process's data segment selector (for DS,ES,SS).
		memset(&desc, 0, sizeof(desc));
		desc.seg_32bit = 1;
		desc.read_exec_only = 0;
		desc.limit_in_pages = 1;
		desc.seg_not_present = 0;
		desc.useable = 1;

		desc.entry_number = emu->datasel / 8;
		desc.base_addr = (uintptr_t)mm->base;
		desc.limit = (mm->size - 1) >> VXPAGESHIFT;
		desc.contents = MODIFY_LDT_CONTENTS_DATA;
		if (modify_ldt(1, &desc, sizeof(desc)) < 0)
			return -1;
	
		// Set up the process's vxemu segment selector (for FS).
		desc.entry_number = emu->emusel / 8;
		desc.base_addr = (uintptr_t)emu;
		desc.limit = (VXCODEBUFSIZE - 1) >> VXPAGESHIFT;
		desc.contents = MODIFY_LDT_CONTENTS_DATA;
		if (modify_ldt(1, &desc, sizeof(desc)) < 0)
			return -1;
		
		emu->ldt_base = (uintptr_t)mm->base;
		emu->ldt_size = mm->size;
	}

#ifdef __x86_64
	// Set up 32-bit mode code and data segments (not vxproc-specific),
	// giving access to the full low 32 bits of linear address space.
	// The code segment is necessary to get into 32-bit compatibility mode;
	// the data segment is needed because Linux for x86-64
	// doesn't give 64-bit processes a "real" data segment by default
	// but instead just loads zero into the data segment selectors!
	emu->runptr.sel = FLATCODE;

	if (!didflat) {
		didflat = 1;
		memset(&desc, 0, sizeof(desc));
		desc.seg_32bit = 1;
		desc.read_exec_only = 0;
		desc.limit_in_pages = 1;
		desc.seg_not_present = 0;
		desc.useable = 1;

		desc.entry_number = FLATCODE / 8;
		desc.base_addr = 0;
		desc.limit = 0xfffff;
		desc.contents = MODIFY_LDT_CONTENTS_CODE;
		if (modify_ldt(1, &desc, sizeof(desc)) < 0)
			return -1;
		
		/*
		 * Linux 2.6.27 has a bug: it does not load the L (long mode)
		 * bit from desc.lm when copying desc into its own
		 * copy of the LDT entry on the kernel stack.
		 * Instead, it leaves L uninitialized, picking up whatever
		 * random bit was left on the kernel stack by the
		 * previous call sequence.  We need L to be 0.
		 * If it ends up 1, the *ljmpq in run64.S will GP fault.
		 * Luckily, we can look for this by asking to read
		 * back the raw LDT bytes.  If we observe this problem,
		 * try to fix it by doing a modify_ldt with base = limit = 0,
		 * which clears the entire stack ldt structure, and then
		 * quickly do another modify_ldt with desc, hoping that
		 * the bit will still be zero when we get there for the
		 * second modify_ldt.  I wish I were making this up.
		 * This is fixed in Linus's git repository, but the Ubuntu
		 * git repositories are still out of date.  See for example
		 * 	http://swtch.com/go/ubuntu-ldt
		 *	http://swtch.com/go/linus-ldt
		 *
		 * Remember, folks, Free Software is only free if your
		 * time has no value.
		 */
		if(modify_ldt(0, ldt, sizeof ldt) < 0)
			return -1;
		if(ldt[1] & 0x00200000) {
			if (vx32_debugxlate)
				vxprint("FLATCODE LDT=%08x %08x; working around\n", ldt[0], ldt[1]);
			desc.limit = 0;
			modify_ldt(1, &desc, sizeof desc);
			desc.limit = 0xfffff;
			modify_ldt(1, &desc, sizeof desc);
			modify_ldt(0, ldt, sizeof ldt);
			if(ldt[1] & 0x00200000) {
				vxprint("cannot work around Linux FLATCODE bug\n");
				errno = EBADE;
				return -1;
			}
			if (vx32_debugxlate)
				vxprint("FLATCODE LDT=%08x %08x\n", ldt[0], ldt[1]);
		}

		desc.entry_number = FLATDATA / 8;
		desc.base_addr = 0;
		desc.limit = 0xfffff;
		desc.contents = MODIFY_LDT_CONTENTS_DATA;
		if (modify_ldt(1, &desc, sizeof(desc)) < 0)
			return -1;
	}

	// Set up a far return vector in emu->retptr
	// for getting back into 64-bit long mode.
	extern void vxrun_return();
	asm volatile("movw %%cs,%0" : "=r" (emu->retptr.sel));
	emu->retptr.ofs = (uint32_t)(intptr_t)vxrun_return;
#endif

	return 0;
}

static void dumpsigcontext(struct sigcontext *ctx)
{
#ifdef i386
	printf(
		"eax %08lx  ebx %08lx\necx %08lx  edx %08lx  "
		"rsi %08lx  rdi %08lx\nrbp %08lx  rsp %08lx\n"
		"eip %08lx  efl %08lx  cs %04x\n"
		"err %08lx  trapno %08lx  cr2 %08lx\n",
		ctx->eax, ctx->ebx, ctx->ecx, ctx->edx,
		ctx->esi, ctx->edi, ctx->ebp, ctx->esp,
		ctx->eip, ctx->eflags, ctx->cs,
		ctx->err, ctx->trapno, ctx->cr2);
#else
	printf(
		"rax %016lx  rbx %016lx\nrcx %016lx  rdx %016lx\n"
		"rsi %016lx  rdi %016lx\nrbp %016lx  rsp %016lx\n"
		"r8  %016lx  r9  %016lx\nr10 %016lx  r11 %016lx\n"
		"r12 %016lx  r13 %016lx\nr14 %016lx  r15 %016lx\n"
		"rip %016lx  efl %016lx  cs %04x  ss %04x\n"
		"err %016lx  trapno %016lx  cr2 %016lx\n",
		ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx,
		ctx->rsi, ctx->rdi, ctx->rbp, ctx->rsp,
		ctx->r8, ctx->r9, ctx->r10, ctx->r11,
		ctx->r12, ctx->r13, ctx->r14, ctx->r15,
		ctx->rip, ctx->eflags, ctx->cs, ctx->__pad0,
		ctx->err, ctx->trapno, ctx->cr2);
#endif
}

#ifdef i386
#define	VX32_BELIEVE_EIP	(ctx->ds == vs - 8)
#define	ctxeip eip
#else
#define	VX32_BELIEVE_EIP	(ctx->cs == FLATCODE)

// On x86-64, make x86 names work for ctx->xxx.
#define	eax rax
#define	ebx rbx
#define	ecx rcx
#define	edx rdx
#define	esi rsi
#define	edi rdi
#define	esp rsp
#define	ebp rbp
#define	ctxeip rip
#endif

static void
fprestore(struct _fpstate *s)
{
	asm volatile("frstor 0(%%eax); fwait\n" : : "a" (s) : "memory");
}

int vx32_sighandler(int signo, siginfo_t *si, void *v)
{
	uint32_t trapeip;
	uint32_t magic;
	uint16_t vs;
	vxproc *vxp;
	vxemu *emu;
	struct sigcontext *ctx;
	ucontext_t *uc;
	mcontext_t *mc;
	int r;

	uc = v;
	mc = &uc->uc_mcontext;

	// same layout, and sigcontext is more convenient...
	ctx = (struct sigcontext*)mc;

	// We can't be sure that vxemu is running,
	// and thus that %VSEG is actually mapped to a
	// valid vxemu.  The only way to tell is to look at %VSEG.

	// First sanity check vxproc segment number.
	asm("movw %"VSEGSTR",%0"
		: "=r" (vs));
	
	if(vx32_debugxlate) vxprint("vx32_sighandler signo=%d eip=%#x esp=%#x vs=%#x\n",
		signo, ctx->ctxeip, ctx->esp, vs);
	if(vx32_debugxlate) dumpsigcontext(ctx);

	if ((vs & 15) != 15)	// 8 (emu), LDT, RPL=3
		return 0;

	// Okay, assume mapped; check for vxemu.
	asm("movl %"VSEGSTR":%1,%0"
		: "=r" (magic)
		: "m" (((vxemu*)0)->magic));
	if (magic != VXEMU_MAGIC)
		return 0;

	// Okay, we're convinced.

	// Find current vxproc and vxemu.
	asm("mov %"VSEGSTR":%1,%0"
		: "=r" (vxp)
		: "m" (((vxemu*)0)->proc));
	emu = vxp->emu;

	// Get back our regular host segment register state,
	// so that thread-local storage and such works.
	vxrun_cleanup(emu);

	// dumpsigcontext(ctx);

	if (VX32_BELIEVE_EIP)
		trapeip = ctx->ctxeip;
	else
		trapeip = 0xffffffff;

	int newtrap;
	switch(signo){
	case SIGSEGV:
	case SIGBUS:
		newtrap = VXTRAP_PAGEFAULT;
		break;
	
	case SIGFPE:
		newtrap = VXTRAP_FLOAT;
		break;
	
	case SIGVTALRM:
		newtrap = VXTRAP_IRQ + VXIRQ_TIMER;
		break;

	case SIGTRAP:
		// Linux sends SIGTRAP when it gets a processor 
		// debug exception, which is caused by single-stepping
		// with the TF bit, among other things.  The processor
		// turns off the TF bit before generating the trap, but
		// it appears that Linux turns it back on for us.
		// Let's use it to confirm that this is a single-step trap.
		if (ctx->eflags & EFLAGS_TF){
			newtrap = VXTRAP_SINGLESTEP;
			ctx->eflags &= ~EFLAGS_TF;
		}else{
			vxprint("Unexpected sigtrap eflags=%#x\n", ctx->eflags);
			newtrap = VXTRAP_SIGNAL + signo;
		}
		break;

	default:
		newtrap = VXTRAP_SIGNAL + signo;
		break;
	}
	
	int replaced_trap = 0;
	if (emu->cpu_trap) {
		// There's already a pending trap!
		// Handle the new trap, and assume that when it
		// finishes, restarting the code at cpu.eip will trigger
		// the old trap again.
		// Have to fix up eip for int 0x30 and syscall instructions.
		if (emu->cpu_trap == VXTRAP_SYSCALL ||
				(emu->cpu_trap&VXTRAP_CATEGORY) == VXTRAP_SOFT)
			emu->cpu.eip -= 2;
		replaced_trap = emu->cpu_trap;
	}
	emu->cpu_trap = newtrap;

	r = vxemu_sighandler(emu, trapeip);

	if (r == VXSIG_SINGLESTEP){
		// Vxemu_sighandler wants us to single step.
		// Execution state is in intermediate state - don't touch.
		ctx->eflags |= EFLAGS_TF;		// x86 TF (single-step) bit
		vxrun_setup(emu);
		return 1;
	}

	// Copy execution state into emu.
	if ((r & VXSIG_SAVE_ALL) == VXSIG_SAVE_ALL) {
		emu->cpu.reg[EAX] = ctx->eax;
		emu->cpu.reg[EBX] = ctx->ebx;
		emu->cpu.reg[ECX] = ctx->ecx;
		emu->cpu.reg[EDX] = ctx->edx;
		emu->cpu.reg[ESI] =  ctx->esi;
		emu->cpu.reg[EDI] = ctx->edi;
		emu->cpu.reg[ESP] = ctx->esp;	// or esp_at_signal ???
		emu->cpu.reg[EBP] = ctx->ebp;
		emu->cpu.eflags = ctx->eflags;
	} else if (r & VXSIG_SAVE_ALL) {
		if (r & VXSIG_SAVE_EAX)
			emu->cpu.reg[EAX] = ctx->eax;
		if (r & VXSIG_SAVE_EBX)
			emu->cpu.reg[EBX] = ctx->ebx;
		if (r & VXSIG_SAVE_ECX)
			emu->cpu.reg[ECX] = ctx->ecx;
		if (r & VXSIG_SAVE_EDX)
			emu->cpu.reg[EDX] = ctx->edx;
		if (r & VXSIG_SAVE_ESI)
			emu->cpu.reg[ESI] =  ctx->esi;
		if (r & VXSIG_SAVE_EDI)
			emu->cpu.reg[EDI] = ctx->edi;
		if (r & VXSIG_SAVE_ESP)
			emu->cpu.reg[ESP] = ctx->esp;	// or esp_at_signal ???
		if (r & VXSIG_SAVE_EBP)
			emu->cpu.reg[EBP] = ctx->ebp;
		if (r & VXSIG_SAVE_EFLAGS)
			emu->cpu.eflags = ctx->eflags;
	}
	r &= ~VXSIG_SAVE_ALL;

	if (r & VXSIG_SAVE_EBX_AS_EIP)
		emu->cpu.eip = ctx->ebx;
	r &= ~VXSIG_SAVE_EBX_AS_EIP;

	if (r & VXSIG_ADD_COUNT_TO_ESP) {
		emu->cpu.reg[ESP] += (uint16_t)(r >> VXSIG_COUNT_SHIFT);
		r &= ~VXSIG_ADD_COUNT_TO_ESP;
		r &= ~(0xFFFF << VXSIG_COUNT_SHIFT);
	}
	
	if (r &  VXSIG_INC_ECX) {
		emu->cpu.reg[ECX]++;
		r &= ~VXSIG_INC_ECX;
	}

	if (r == VXSIG_TRAP) {
		if (emu->trapenv == NULL)
			return 0;
		emu->cpu.traperr = ctx->err;
		// Usually, ctx->cr2 == si->si_addr.
		// But on a segmentation fault (as opposed to a paging fault),
		// cr2 is not updated and the kernel sends an si_addr == 0.
		// Be sure to use si_addr, not cr2.
		emu->cpu.trapva = (uint32_t)(uintptr_t)si->si_addr;
		memmove(mc->gregs, emu->trapenv->gregs, sizeof emu->trapenv->gregs);
		
		return 1;
	}

	// The signal handler is confused; so are we.
	return 0;
}
