/**
 * $Id$
 * Copyright (C) 2008 - 2014 Nils Asmussen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <mem/kheap.h>
#include <mem/pagedir.h>
#include <mem/virtmem.h>
#include <sys/test.h>
#include <task/proc.h>
#include <task/thread.h>
#include <common.h>
#include <video.h>

#include "testutils.h"

/* forward declarations */
static void test_vmm();
static void test_1();
static void test_2();

/* our test-module */
sTestModule tModVmm = {
	"Virtual Memory Manager",
	&test_vmm
};

static void test_vmm() {
	test_1();
	test_2();
}

static void test_1() {
	VMRegion *rno,*rno2,*rno3;
	uintptr_t addr;
	Thread *t = Thread::getRunning();
	Proc *p = t->getProc();
	test_caseStart("Testing VirtMem::add() and VirtMem::unmap()");

	checkMemoryBefore(true);
	t->reserveFrames(1);
	addr = 0x1000;
	test_assertTrue(p->getVM()->map(&addr,PAGE_SIZE,PAGE_SIZE,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_FIXED,NULL,0,&rno) == 0);
	p->getVM()->unmap(rno);
	t->discardFrames();
	checkMemoryAfter(true);

	checkMemoryBefore(true);
	t->reserveFrames(9);
	addr = 0x1000;
	test_assertTrue(p->getVM()->map(&addr,PAGE_SIZE * 2,PAGE_SIZE * 2,PROT_READ | PROT_EXEC,
			MAP_SHARED | MAP_FIXED,NULL,0,&rno) == 0);
	addr = 0x8000;
	test_assertTrue(p->getVM()->map(&addr,PAGE_SIZE * 3,PAGE_SIZE * 3,PROT_READ,
			MAP_PRIVATE | MAP_FIXED,NULL,0,&rno2) == 0);
	addr = 0x10000;
	test_assertTrue(p->getVM()->map(&addr,PAGE_SIZE * 4,PAGE_SIZE * 4,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_GROWABLE | MAP_FIXED,NULL,0,&rno3) == 0);
	p->getVM()->unmap(rno);
	p->getVM()->unmap(rno2);
	p->getVM()->unmap(rno3);
	t->discardFrames();
	checkMemoryAfter(true);

	checkMemoryBefore(true);
	t->reserveFrames(8);
	test_assertTrue(p->getVM()->map(NULL,PAGE_SIZE,PAGE_SIZE,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_STACK | MAP_GROWABLE | MAP_GROWSDOWN,NULL,0,&rno) == 0);
	test_assertTrue(p->getVM()->map(NULL,PAGE_SIZE * 2,PAGE_SIZE * 2,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_STACK | MAP_GROWABLE | MAP_GROWSDOWN,NULL,0,&rno2) == 0);
	test_assertTrue(p->getVM()->map(NULL,PAGE_SIZE * 5,PAGE_SIZE * 5,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_STACK | MAP_GROWABLE,NULL,0,&rno3) == 0);
	p->getVM()->unmap(rno);
	p->getVM()->unmap(rno2);
	p->getVM()->unmap(rno3);
	t->discardFrames();
	checkMemoryAfter(true);

	/* doesn't work on mmix since we would have to leave the kernel and enter it again in order to
	 * get a new kernel-stack */
#ifndef __mmix__
	pid_t cpid = Proc::clone(0);
	if(cpid == 0) {
		Proc::terminate();
		A_UNREACHED;
	}
	test_assertTrue(cpid > 0);
	Proc *clone = Proc::getByPid(cpid);

	checkMemoryBefore(true);
	t->reserveFrames(4);
	test_assertTrue(p->getVM()->map(NULL,PAGE_SIZE * 4,PAGE_SIZE * 4,PROT_READ,MAP_SHARED,NULL,0,&rno) == 0);
	test_assertTrue(p->getVM()->join(rno->virt(),clone->getVM(),&rno2,0,0) == 0);
	clone->getVM()->unmap(rno2);
	p->getVM()->unmap(rno);
	t->discardFrames();
	checkMemoryAfter(true);

	Proc::waitChild(NULL,-1,0);
	test_assertTrue(Proc::getByPid(cpid) == NULL);
#endif

	test_caseSucceeded();
}

static void test_2() {
	VMRegion *rno;
	uintptr_t addr,start,end;
	Thread *t = Thread::getRunning();
	Proc *p = t->getProc();
	test_caseStart("Testing VirtMem::grow()");

	checkMemoryBefore(true);
	t->reserveFrames(5);
	addr = 0x1000;
	test_assertTrue(p->getVM()->map(&addr,PAGE_SIZE,PAGE_SIZE,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_FIXED | MAP_GROWABLE,NULL,0,&rno) == 0);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),3),end);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),-2),end);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),1),end);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),-3),end);
	p->getVM()->unmap(rno);
	t->discardFrames();
	checkMemoryAfter(true);

	checkMemoryBefore(true);
	t->reserveFrames(7);
	test_assertTrue(p->getVM()->map(NULL,PAGE_SIZE,PAGE_SIZE,PROT_READ | PROT_WRITE,
			MAP_PRIVATE | MAP_STACK | MAP_GROWABLE | MAP_GROWSDOWN,NULL,0,&rno) == 0);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),3),start);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),2),start);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),-4),start);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),1),start);
	p->getVM()->getRegRange(rno,&start,&end,true);
	test_assertSSize(p->getVM()->grow(rno->virt(),-1),start);
	p->getVM()->unmap(rno);
	t->discardFrames();
	checkMemoryAfter(true);

	test_caseSucceeded();
}
