/* 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;
	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
//	- an environment 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, except 
// that it also must not grant write access to a read-only 
// page.
//
// 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);

//        printf("dstenv->env_pgdir=%08lx\n", dstenv->env_pgdir);
        // Copy src pte mapping to dest pte with appropriate perms
        *dstpte = (page2pa(pg) & ~0xFFF) | perm;
//        printf("dstpte %08lx = %08lx\n", dstpte, *dstpte);
        
        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.
//
// srcva and perm should have the same restrictions as they had
// in sys_mem_map.
//
// 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.
//
// Again, dstva should have the same restrictions as it had in
// sys_mem_map.  If it violates these restrictions, assume that it is
// zero.
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();
}

// Eat a child. This replaces the current environment's address space
// and trapframe with that of the specified child, then consumes the
// child. Truly bizarre things can happen if this is not used in the
// context of an exec(). The eschaton may be immanentized!
//
// It is a matter for philosophers to decide whether the resulting
// process, which retains the parent's env_id, but has the child's
// address space (and hence code), is the parent or the child. (In
// Soviet Russia, child eats you!)
//
// Eating gifted children may raise your IQ. [Frumkes 1993]
int
sys_env_eat_child(u_int envid)
{
        struct Env *env;
        int r;

        // You can't very well eat someone else's children. That's
        // just wrong!
        if ((r = envid2env(envid, &env, 0)) != 0)
                return r;

        struct Page *cur_pgdir = pa2page(env->env_cr3);
        cur_pgdir->pp_ref++;
        
        // Free the current environment's address space (but don't put
        // it back in the free list.)
        env_free(curenv);       // This just feels wrong, but it isn't!
        LIST_REMOVE(curenv, env_link);

        // Copy address space
        env_setup_vm(curenv);

        int i;
        for (i = 0; i < PDX(UTOP); i++) {
                if (env->env_pgdir[i] & PTE_P) {
                        Pte *pte = (Pte *) KADDR(PTE_ADDR(env->env_pgdir[i]));
                        int j;
                        for (j = 0; j < PTE2PT; j++) {
                                if (!(*(pte+j) & PTE_P))
                                        continue;
                                
                                struct Page *pg = pa2page(PTE_ADDR(*(pte + j)));
                                page_insert(curenv->env_pgdir, pg,
                                            (i << PDSHIFT) + (j << PGSHIFT),
                                            *(pte+j) & 0xFFF);
                        }
                }
        }

        // Copy trapframe
        *UTF = curenv->env_tf = env->env_tf;

        
        // Eat the child!
        env_free(env);
        // Yum!

        page_decref(cur_pgdir);

        curenv->env_status = ENV_RUNNABLE;

        return 0;
}

// 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;

        case SYS_env_eat_child:
                return sys_env_eat_child(a1);
                
                
        default:
                return -E_INVAL;
        }
}

