/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>

#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/syscall.h>
#include <kern/console.h>
#include <kern/sched.h>

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

// read a character from the system console
static int
sys_cgetc(void)
{
	int c;

	// The cons_getc() primitive doesn't wait for a character,
	// but the sys_cgetc() system call does.
	while ((c = cons_getc()) == 0)
		; /* spin */

	return c;
}

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

// destroy a given environment
// (possibly the currently running environment)
static int
sys_env_destroy(u_int envid)
{
	int r;
	struct Env *e;

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

// Deschedule current environment and pick a different one to run.
static void
sys_yield(void)
{
	sched_yield();
}

//
// 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)
{
//        printf("sys_mem_alloc: %08lx, %08lx, %08lx\n", envid, va, perm);
        
        // Validate envid and convert to env
        struct Env *env;
        int err;

        if ((err = envid2env(envid, &env, 1)) != 0)
                return err;

        // Validate va
        if (va >= UTOP)
                return -E_INVAL;

        // Validate perm
        if (!((perm & PTE_U) && (perm & PTE_P)))
                return -E_INVAL;
        if ((perm & ~(PTE_U | PTE_P | PTE_AVAIL | PTE_W)) != 0)
                return -E_INVAL;

        // Remove existing mapping
        page_remove(env->env_pgdir, va);
        
        // Allocate a page
        struct Page *pg;
        if ((err = page_alloc(&pg)) != 0)
                return err;

        // Map it
        if ((err = page_insert(env->env_pgdir, pg, va, perm)) != 0)
                return err;
        
        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)
{
        // Validate envids and convert to envs
        struct Env *srcenv, *dstenv;
        int err;

        if ((err = envid2env(srcid, &srcenv, 1)) != 0)
                return err;
        if ((err = envid2env(dstid, &dstenv, 1)) != 0)
                return err;
        
        // Validate vas
        if (srcva >= UTOP || dstva >= UTOP)
                return -E_INVAL;

        srcva -= (srcva % BY2PG);
        dstva -= (dstva % BY2PG);
        
        // Validate perm
        if (!((perm & PTE_U) && (perm & PTE_P)))
                return -E_INVAL;
        if ((perm & ~(PTE_U | PTE_P | PTE_AVAIL | PTE_W)) != 0)
                return -E_INVAL;

        // Find dest pte
        Pte *dstpte;
        if ((err = pgdir_walk(dstenv->env_pgdir, dstva, 1, &dstpte)) != 0)
                return err;

        struct Page *pg = page_lookup(srcenv->env_pgdir, srcva, NULL);
        pg->pp_ref++;
//        printf("incrementing refcount to %ld on page %08lx\n", pg->pp_ref, page2pa(pg));

        // Remove any existing mapping in dest
        page_remove(dstenv->env_pgdir, dstva);
        
        // Copy src pte mapping to dest pte with appropriate perms
        *dstpte = (page2pa(pg) & ~0xFFF) | perm;

        return 0;
}

// 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)
{
        // Validate envid and convert to env
        struct Env *env;
        int err;
        
        if ((err = envid2env(envid, &env, 1)) != 0)
                return err;

        // Validate va
        if (va >= UTOP)
                return -E_INVAL;

        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(void)
{
        struct Env *newenv;
        int err;

        err = env_alloc(&newenv, curenv->env_id);
        if (err)
                return err;

        newenv->env_status = ENV_NOT_RUNNABLE;
        newenv->env_tf = *UTF;
        newenv->env_tf.tf_eax = 0;
        
        return newenv->env_id;
}

// 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)
{
        struct Env *env;

        if (envid2env(envid, &env, 1) != 0)
                return -E_INVAL;

        env->env_tf = *tf;

        return 0;
}

// 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_status(u_int envid, u_int status)
{
        struct Env *env;
        int err;
        
        if ((err = envid2env(envid, &env, 1)) != 0)
                return err;

        if (status == ENV_RUNNABLE || status == ENV_NOT_RUNNABLE) {
                env->env_status = status;
                return 0;
        } else {
                return -E_INVAL;
        }
}

// 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_entry(u_int envid, u_int func)
{
        struct Env *env;
        int err;
        
        if ((err = envid2env(envid, &env, 1)) != 0)
                return err;

        env->env_pgfault_entry = TRUP(func);
        
        return 0;
}

// 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)
{
        struct Env *env;
        int r;

        if ((r = envid2env(envid, &env, 0)) != 0)
                return r;

        if (env->env_ipc_recving == 0)
                return -E_IPC_NOT_RECV;
        
        if (srcva != 0) {
                if (env->env_ipc_dstva != 0) {
                        // Validate perm
                        if (!((perm & PTE_U) && (perm & PTE_P)))
                                return -E_INVAL;
                        if ((perm & ~(PTE_U | PTE_P | PTE_AVAIL | PTE_W)) != 0)
                                return -E_INVAL;
                        
                        // Find dest pte
                        Pte *dstpte;
                        if ((r = pgdir_walk(env->env_pgdir,
                                            env->env_ipc_dstva,
                                            1, &dstpte)) != 0)
                                return r;
                        
                        struct Page *pg = page_lookup(curenv->env_pgdir, srcva, NULL);
                        pg->pp_ref++;
                        
                        // Remove any existing mapping in dest
                        page_remove(env->env_pgdir, env->env_ipc_dstva);
                        
                        // Copy src pte mapping to dest pte with appropriate perms
                        *dstpte = (page2pa(pg) & ~0xFFF)| perm;

                        env->env_ipc_perm = perm;
                }
        }

        env->env_ipc_recving = 0;
        env->env_ipc_from = curenv->env_id;
        env->env_ipc_value = value;
        env->env_status = ENV_RUNNABLE;
        
        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)
{
        curenv->env_ipc_recving = 1;
        curenv->env_ipc_dstva = dstva;
        curenv->env_status = ENV_NOT_RUNNABLE;
        sched_yield();
}


// 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_cputs:
                sys_cputs((char *)a1);
                return 0;

        case SYS_cgetc:
                return sys_cgetc();

        case SYS_getenvid:
                return sys_getenvid();

        case SYS_env_destroy:
                return sys_env_destroy(a1);

        case SYS_yield:
                sys_yield();
                return 0;

        case SYS_env_alloc:
                return sys_env_alloc();

        case SYS_set_trapframe:
                return sys_set_trapframe(a1, (struct Trapframe *)a2);

        case SYS_set_status:
                return sys_set_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_pgfault_entry:
                return sys_set_pgfault_entry(a1, a2);

        case SYS_ipc_can_send:
                return sys_ipc_can_send(a1, a2, a3, a4);

        case SYS_ipc_recv:
                sys_ipc_recv(a1);
                return 0;
                
        default:
                return -E_INVAL;
        }
}

