/*  This file is part of Whitix.
 *
 *  Whitix 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.
 *
 *  Whitix 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 Whitix; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <fs/vfs.h>
#include <vmm.h>
#include <llist.h>
#include <malloc.h>
#include <module.h>
#include <typedefs.h>
#include <i386/i386.h>
#include <slab.h>
#include <user_acc.h>

struct Cache* areaCache,*mapCache;
SYMBOL_EXPORT(areaCache);
SYMBOL_EXPORT(mapCache);

/***********************************************************************
 *
 * FUNCTION: VmLookupAddress
 *
 * DESCRIPTION: Look up an address in the process's region list.
 *
 * PARAMETERS: process - the process in question.
 *			   address - the address in question.
 *
 * RETURNS: area that the address is contained in.
 *
 ***********************************************************************/

struct VMArea* VmLookupAddress(struct Process* process,DWORD address)
{
	struct VMArea* curr;

	ListForEachEntry(curr,&process->areaList,list)
	{ 
		/* Ordered by vm area start address, so don't have to search through
		the whole list if the address has already been passed. */
		if (curr->start > address)
			break;

		/* Is the address in the virtual mapping? */
		if (address >= curr->start && address < (curr->start+curr->length))
			return curr;
	}

	return NULL;
}

SYMBOL_EXPORT(VmLookupAddress);

/***********************************************************************
 *
 * FUNCTION: VmLookupPage
 *
 * DESCRIPTION: Looks up a page in a vNode by file offset to see if anyone
 *				has mapped the page already.
 *
 * PARAMETERS: vNode - vNode that contains the shared list.
 *			   offset - file offset needed.
 *
 * RETURNS: the virtual page containing the file data.
 *
 ***********************************************************************/

struct VMMapPage* VmLookupPage(struct VNode* vNode,DWORD offset)
{
	struct VMMapPage* page;

	/* Has anyone else mapped it in? */
	if (!vNode || vNode->refs == 1 || ListEmpty(&vNode->sharedList))
		return NULL;

	/* Loop through the shared list and return the one with the offset we need. */
	ListForEachEntry(page,&vNode->sharedList,list)	
		if (page->offset == offset)
			return page;

	/* Not found - it will be mapped in and added to the shared list */
	return NULL;
}

SYMBOL_EXPORT(VmLookupPage);

/***********************************************************************
 *
 * FUNCTION: VmCreateMappedPage
 *
 * DESCRIPTION: Looks up a page in a vNode by file offset.
 *
 * PARAMETERS: vNode - vNode that contains the shared list.
 *			   offset - file offset needed.
 *
 * RETURNS: the virtual page containing the file data.
 *
 ***********************************************************************/

struct VMMapPage* VmCreateMappedPage(DWORD offset,struct PhysPage* page)
{
	struct VMMapPage* mappedPage=(struct VMMapPage*)MemCacheAlloc(mapCache);

	if (!mappedPage)
		return NULL;

	mappedPage->offset=offset;
	mappedPage->page=page;

	return mappedPage;
}

SYMBOL_EXPORT(VmCreateMappedPage);

/***********************************************************************
 *
 * FUNCTION: VmFreeMappedPage
 *
 * DESCRIPTION: Free a mapped page by physical address.
 *
 * PARAMETERS: vNode - VFS node containing the reference to the page.
 *			   physAddr - physical address of the page.
 *
 * RETURNS: Usual error codes.
 *
 ***********************************************************************/

int VmFreeMappedPage(struct VNode* vNode,DWORD physAddr)
{
	struct VMMapPage* page=NULL;

	if (!vNode)
		return -EFAULT;

	/* Remove a physical page from the list, because there are no references and it will be freed */

	ListForEachEntry(page,&vNode->sharedList,list)
		if (page->page->physAddr == physAddr)
		{
			ListRemove(&page->list);
			return 0;
		}

	return -ENOENT;
}

SYMBOL_EXPORT(VmFreeMappedPage);

/***********************************************************************
 *
 * FUNCTION:	VmWaitOnPage
 *
 * DESCRIPTION: Wait for another thread to stop altering the page. 
 *				A single waitqueue is shared between a number of pages 
 *				(one per 4mb+ or so) since there are not many conflicts 
 *				for a single page - it is very unlikely that a page will 
 *				be locked and someone else try to access it.
 *
 * PARAMETERS:	page - page in question.
 *
 * RETURNS:		Nothing.
 *
 ***********************************************************************/

void VmWaitOnPage(struct VMMapPage* page)
{
	WaitQueue* waitQueue;
	
	if (!(page->flags & MPAGE_BUSY))
		return;

	waitQueue=PageGetWaitQueue(page->page);

	if (UNLIKELY(page->flags & MPAGE_BUSY))
		WAIT_ON(*waitQueue,!(page->flags & MPAGE_BUSY));
}

SYMBOL_EXPORT(VmWaitOnPage);

/***********************************************************************
 *
 * FUNCTION: VmLockPage
 *
 * DESCRIPTION: Locks a page to alter data.
 *
 * PARAMETERS: page - page in question.
 *
 * RETURNS: Nothing.
 *
 ***********************************************************************/

void VmLockPage(struct VMMapPage* page)
{
	VmWaitOnPage(page);
	page->flags |= MPAGE_BUSY;
}

SYMBOL_EXPORT(VmLockPage);

/***********************************************************************
 *
 * FUNCTION: VmUnlockPage
 *
 * DESCRIPTION: Unlock the page, let any thread access it.
 *
 * PARAMETERS: page - page in question.
 *
 * RETURNS: Nothing.
 *
 ***********************************************************************/

void VmUnlockPage(struct VMMapPage* page)
{
	page->flags &= ~MPAGE_BUSY;
	WakeUp(PageGetWaitQueue(page->page));
}

SYMBOL_EXPORT(VmUnlockPage);

int VirtCheckArea(void* beginAddr,DWORD size,int mask)
{
	DWORD begin=(DWORD)beginAddr;
	struct VMArea* area,*next;

	/* Don't have to check */
	if (!size)
		return 0;

	area=VmLookupAddress(current,begin);

	if (!area)
		return -EFAULT;

	/* Cannot write to a read-only area */
	if (!(area->protection & PAGE_RW) && (mask & VER_WRITE))
		return -EFAULT;

	/* Doesn't overrun */
	if ((area->start+area->length)-begin >= size)
		return 0;

	/* Check when size overruns the area */
	while (1)
	{
		/* Assumes all areas are at least readable */
		if (!(area->protection & PAGE_RW) && (mask & VER_WRITE))
			return -EFAULT;

		/* Cannot let the user let the kernel write to it's own pages */
		if (!(area->protection & PAGE_USER))
			return -EFAULT;

		if ((area->start+area->length)-begin >= size)
			break;

		next=ListEntry(area->list.next,struct VMArea,list);
		if (area->list.next == &current->areaList || next->start == area->start+area->length) /* Does next even exist? */
			return -EFAULT;

		area=next;
	}

	return 0;
}

SYMBOL_EXPORT(VirtCheckArea);

int VmInit()
{
	/* Create caches */
	areaCache=MemCacheCreate("VMArea Cache",sizeof(struct VMArea),NULL,NULL,0);
	mapCache=MemCacheCreate("Mapped Page Cache",sizeof(struct VMMapPage),NULL,NULL,0);

	if (!areaCache || !mapCache)
		return -ENOMEM; /* TODO: Panic. */

	return 0;
}

ModuleInit(VmInit);
