/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/error.h>
#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/syscall.h>
#include <kern/console.h>
#include <kern/printf.h>
#include <kern/sched.h>

#include "bochs.h"

// return the current environment id
static u_int
sys_getenvid(void)
{
  return curenv->env_id;
}

// print a string to the screen.
static void
sys_cputs(char *s)
{
  page_fault_mode = PFM_KILL;
  printf("%s", TRUP(s));
  page_fault_mode = PFM_NONE;
}

// deschedule current environment
static void
sys_yield(void)
{
	sched_yield();
}

// destroy the current environment
static int
sys_env_destroy(u_int envid)
{
	int r;
	struct Env *e;

	if ((r=envid2env(envid, &e, 1)) < 0)
		return r;
	// printf("[%08x] destroying %08x\n", curenv->env_id, e->env_id);
	env_destroy(e);
	return 0;
}

// Block until a value is ready.  Record that you want to receive,
// mark yourself not runnable, and then give up the CPU.
static void
sys_ipc_recv(u_int dstva)
{
	if(curenv->env_ipc_recving)
		panic("already recving!");

	if (dstva >= UTOP)
		panic("invalid dstva");

	curenv->env_ipc_recving = 1;
	curenv->env_ipc_dstva = dstva;
	curenv->env_status = ENV_NOT_RUNNABLE;
	sched_yield();
}

// Try to send 'value' to the target env 'envid'.
// If va != 0, then also send page currently mapped at va,
// so that receiver gets a duplicate mapping of the same page.
//
// The send fails with a return value of -E_IPC_NOT_RECV if the
// target has not requested IPC with sys_ipc_recv.
//
// Otherwise, the send succeeds, and the target's ipc fields are
// updated as follows:
//    env_ipc_recving is set to 0 to block future sends
//    env_ipc_from is set to the sending envid
//    env_ipc_value is set to the 'value' parameter
// The target environment is marked runnable again.
//
// Return 0 on success, < 0 on error.
//
// If the sender sends a page but the receiver isn't asking for one,
// then no page mapping is transferred but no error occurs.
//
// Hint: you will find envid2env() useful.
static int
sys_ipc_can_send(u_int envid, u_int value, u_int srcva, u_int perm)
{
	int r;
	struct Env *e;
	struct Page *p;

	if ((r=envid2env(envid, &e, 0)) < 0)
		return r;
	if (!e->env_ipc_recving)
		return -E_IPC_NOT_RECV;

	if (srcva != 0 && e->env_ipc_dstva != 0) {

		if (srcva >= UTOP)
			return -E_INVAL;
		if (((~perm)&(PTE_U|PTE_P)) ||
		    (perm&~(PTE_USER)))
			return -E_INVAL;

		p = page_lookup(curenv->env_pgdir, srcva, 0);
		if (p == 0) {
			printf("[%08x] page_lookup %08x failed in sys_ipc_can_send\n",
				curenv->env_id, srcva);
			return -E_INVAL;
		}
		r = page_insert(e->env_pgdir, p, e->env_ipc_dstva, perm);
		if (r < 0)
			return r;

		e->env_ipc_perm = perm;
	} else {
		e->env_ipc_perm = 0;
	}

	e->env_ipc_recving = 0;
	e->env_ipc_from = curenv->env_id;
	e->env_ipc_value = value;
	e->env_status = ENV_RUNNABLE;
	return 0;
}

// Set envid's pagefault handler entry point and exception stack.
// (xstacktop points one byte past exception stack).
//
// Returns 0 on success, < 0 on error.
static int
sys_set_pgfault_handler(u_int envid, u_int func, u_int xstacktop)
{
  struct Env *env;

  if (envid2env(envid, &env, 1)) 
    return -E_BAD_ENV;

  env->env_pgfault_handler = TRUP(func);
  env->env_xstacktop = xstacktop;

  return 0;
}

//
// Allocate a page of memory and map it at 'va' with permission
// 'perm' in the address space of 'envid'.
//
// If a page is already mapped at 'va', that page is unmapped as a
// side-effect.
//
// perm -- PTE_U|PTE_P are required, 
//         PTE_AVAIL|PTE_W are optional,
//         but no other bits are allowed (return -E_INVAL)
//
// Return 0 on success, < 0 on error
//	- va must be < UTOP
//	- env may modify its own address space or the address space of its children
// 
static int
sys_mem_alloc(u_int envid, u_int va, u_int perm)
{
  struct Page *pp;
  struct Env *env;

  //  check query first
  //  Check perm bits
  perm = perm | PTE_U | PTE_P;
  if (perm & (~(PTE_USER)))
    // Someone wants more bits than allowed
    return -E_INVAL;

  if (va > UTOP) 
    return -E_INVAL;

  if (envid2env(envid, &env, 1))
    return -E_BAD_ENV;
  
  if (page_alloc(&pp))
    return -E_NO_MEM;

  if (page_insert(env->env_pgdir, pp, va, perm)) {
    page_free(pp);
    return -E_NO_MEM;
  }
  
  return 0;
}

// Map the page of memory at 'srcva' in srcid's address space
// at 'dstva' in dstid's address space with permission 'perm'.
// Perm has the same restrictions as in sys_mem_alloc.
// (Probably we should add a restriction that you can't go from
// non-writable to writable?)
//
// Return 0 on success, < 0 on error.
//
// Cannot access pages above UTOP.
static int
sys_mem_map(u_int srcid, u_int srcva, u_int dstid, u_int dstva, u_int perm)
{
  struct Env *srcenv, *dstenv;
  struct Page *pp;

  //  Check perm bits
  perm = perm | PTE_U | PTE_P;
  if (perm & (~(PTE_USER))) {
    // Someone wants more bits than allowed
    return -E_INVAL;
  }
  if ((srcva > UTOP) || (dstva > UTOP)) {
    return -E_INVAL;
  }
  if (envid2env(srcid, &srcenv, 1)){
    return -E_BAD_ENV;
  }
  if (envid2env(dstid, &dstenv, 1)) {
    return -E_BAD_ENV;
  }
  if ((pp = page_lookup(srcenv->env_pgdir, srcva, NULL)) != NULL) {
    if (!page_insert(dstenv->env_pgdir, pp, dstva, perm)) {
      return 0;
    }
  } else {
    warn("sys_mem_map failed to map %d : 0x%x  to  %x : 0x%x",
	 srcid, srcva, dstid, dstva);
  }
  return -E_INVAL;
}

// Unmap the page of memory at 'va' in the address space of 'envid'
// (if no page is mapped, the function silently succeeds)
//
// Return 0 on success, < 0 on error.
//
// Cannot unmap pages above UTOP.
static int
sys_mem_unmap(u_int envid, u_int va)
{
  struct Env *env;

  if (va > UTOP)
    return -E_INVAL;

  if (envid2env(envid, &env, 1))
    return -E_BAD_ENV;

  page_remove(env->env_pgdir, va);
  return 0;
}

// Allocate a new environment.
//
// The new child is left as env_alloc created it, except that
// status is set to ENV_NOT_RUNNABLE and the register set is copied
// from the current environment.  In the child, the register set is
// tweaked so sys_env_alloc returns 0.
//
// Returns envid of new environment, or < 0 on error.
static int
sys_env_alloc(char *name)
{
  struct Env *env;

  if (env_alloc(&env, curenv->env_id)) {
    return -E_NO_FREE_ENV;
  }

  env->env_status = ENV_NOT_RUNNABLE;
  bcopy(UTF, &(env->env_tf), sizeof(struct Trapframe));
  env->env_tf.tf_eax = 0;
  strncpy(env->env_name, name, ENV_NAME_LEN);
  env->env_name[ENV_NAME_LEN-1] = '\0';

  return env->env_id;
}

// Set envid's env_status to status. 
//
// Returns 0 on success, < 0 on error.
// 
// Return -E_INVAL if status is not a valid status for an environment.
static int
sys_set_env_status(u_int envid, u_int status)
{
  struct Env *env;

  if ((status != ENV_FREE) &&
      (status != ENV_RUNNABLE) &&
      (status != ENV_NOT_RUNNABLE))
    return -E_INVAL;

  if (envid2env(envid, &env, 1))
    return -E_BAD_ENV;

  env->env_status = status;
  return 0;
}

// Set envid's trap frame to tf.
//
// Returns 0 on success, < 0 on error.
//
// Return -E_INVAL if the environment cannot be manipulated.
static int
sys_set_trapframe(u_int envid, struct Trapframe *tf)
{
	int r;
	struct Env *e;
	struct Trapframe ltf;

	page_fault_mode = PFM_KILL;
	ltf = *TRUP(tf);
	page_fault_mode = PFM_NONE;

	ltf.tf_eflags |= FL_IF;
	ltf.tf_cs |= 3;

	if ((r=envid2env(envid, &e, 1)) < 0)
		return r;
	if (e == curenv)
		*UTF = ltf;
	else
		e->env_tf = ltf;
	return 0;
}

static void
sys_panic(char *msg)
{
	// no page_fault_mode -- we are trying to panic!
	panic("%s", TRUP(msg));
}

static int
sys_cgetc()
{
  return cons_getc();
}

// Dispatches to the correct kernel function, passing the arguments.
int
syscall(u_int sn, u_int a1, u_int a2, u_int a3, u_int a4, u_int a5)
{
  // printf("syscall %d %x %x %x from env %08x\n", sn, a1, a2, a3, curenv->env_id);

  switch (sn) {
  case SYS_getenvid:
    return sys_getenvid();
  case SYS_cputs:
    sys_cputs((char *)a1);
    return 0;
  case SYS_yield:
    sys_yield();
    return 0;
  case SYS_env_destroy:
    sys_env_destroy(a1);
    return 0;
  case SYS_env_alloc:
    return sys_env_alloc((char *)a1);
  case SYS_ipc_can_send:
    return sys_ipc_can_send(a1, a2, a3, a4);
  case SYS_ipc_recv:
    sys_ipc_recv(a1);
    return 0;
  case SYS_set_pgfault_handler:
    return sys_set_pgfault_handler(a1, a2, a3);
  case SYS_set_env_status:
    return sys_set_env_status(a1, a2);
  case SYS_mem_alloc:
    return sys_mem_alloc(a1, a2, a3);
  case SYS_mem_map:
    return sys_mem_map(a1,a2,a3,a4,a5);
  case SYS_mem_unmap:
    return sys_mem_unmap(a1, a2);
  case SYS_set_trapframe:
    return sys_set_trapframe(a1, (struct Trapframe *)a2);
  case SYS_panic:
    sys_panic((char *)a1);
    return -E_INVAL;
  case SYS_cgetc:
    return sys_cgetc();

  default:
    return -E_INVAL;

  }
}

