/*-
 * Copyright (c) 2002-2018 The UbixOS Project.
 * All rights reserved.
 *
 * This was developed by Christopher W. Olsen for the UbixOS Project.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1) Redistributions of source code must retain the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors in the documentation and/or
 *    other materials provided with the distribution.
 * 3) Neither the name of the UbixOS Project nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <vmm/vmm.h>
#include <sys/io.h>
#include <ubixos/kpanic.h>
#include <lib/kprintf.h>
#include <lib/kmalloc.h>
#include <ubixos/vitals.h>
#include <ubixos/spinlock.h>
#include <assert.h>
//MrOlsen (2016-01-11) NOTE: Need to Seperate Out CPU Specific Stuff Over Time
#include <i386/cpu.h>
static uint32_t freePages			= 0;
static struct spinLock vmmSpinLock	= SPIN_LOCK_INITIALIZER;
//static struct spinLock vmmCowSpinLock = SPIN_LOCK_INITIALIZER;
int numPages						= 0x0;
mMap *vmmMemoryMap					= ( mMap * ) VMM_MMAP_ADDR_RMODE;
/************************************************************************
 Function: void vmm_memMapInit();
 Description: This Function Initializes The Memory Map For the System
 Notes:
 02/20/2004 - Made It Report Real And Available Memory
 ************************************************************************/
int vmm_memMapInit() {
	int i		= 0x0;
	int memStart	= 0x0;
	//	Count System Memory
	numPages = countMemory();
	//	Set Memory Map To Point To First Physical Page That We Will Use
	vmmMemoryMap = ( mMap * ) VMM_MMAP_ADDR_RMODE;
	//	Initialize Map Make All Pages Not Available
	for( i = 0x0; i < numPages; i++ ) {
		vmmMemoryMap[i].cowCounter	= 0x0;
		vmmMemoryMap[i].status		= memNotavail;
		vmmMemoryMap[i].pid			= vmmID;
		vmmMemoryMap[i].pageAddr	= i * PAGE_SIZE;
	}
	//	Calculate Start Of Free Memory
	memStart	= ( 0x101000 / 0x1000 );
	memStart 	+= ( ( ( sizeof( mMap ) * numPages ) + ( sizeof( mMap ) - 1 ) ) / 0x1000 );
	//	Initialize All Free Pages To Available
	vmmMemoryMap[( 0x100000 / 0x1000 )].status = memAvail;
	freePages++;
	for( i = memStart; i < numPages; i++ ) {
		vmmMemoryMap[i].status = memAvail;
		freePages++;
	}
	if( systemVitals )
		systemVitals->freePages = freePages;
	//	Print Out Amount Of Memory
	kprintf( "Real Memory:      %iKB\n", numPages * 4 );
	kprintf( "Available Memory: %iKB\n", freePages * 4 );
	//	Return
	return(0x0);
}
/************************************************************************
 Function: int countMemory();
 Description: This Function Counts The Systems Physical Memory
 Notes:
 02/20/2004 - Inspect For Quality And Approved
 ************************************************************************/
int countMemory() {
	register uInt32 *mem		= 0x0;
	unsigned long memCount		= -1;
	unsigned long tempMemory	= 0x0;
	unsigned short memKb		= 8;
	unsigned char irq1State;
	unsigned char irq2State;
	unsigned long cr0			= 0x0;
	/*
	 * Save The States Of Both IRQ 1 And 2 So We Can Turn Them Off And Restore
	 * Them Later
	 */
	irq1State = inportByte( 0x21 );
	irq2State = inportByte( 0xA1 );
	// Turn Off IRQ 1 And 2 To Prevent Chances Of Faults While Examining Memory
	outportByte( 0x21, 0xFF );
	outportByte( 0xA1, 0xFF );
	//	Save The State Of Register CR0
	cr0	= rcr0();
  /*
   asm volatile (
   "movl %%cr0, %%ebx\n"
   : "=a" (cr0)
   :
   : "ebx"
   );
   */
	asm volatile ( "wbinvd" );
	load_cr0( cr0 | 0x00000001 | 0x40000000 | 0x20000000 );
  /*
   asm volatile (
   "movl %%ebx, %%cr0\n"
   :
   : "a" (cr0 | 0x00000001 | 0x40000000 | 0x20000000)
   : "ebx"
   );
   */
	while( memKb < 4096 && memCount != 0 ) {
		memKb++;
		if( memCount == -1 )
			memCount = 8388608;
		else
			memCount += 1024 * 1024;
		mem = ( uInt32 * ) memCount;
		tempMemory = *mem;
		*mem = 0x55AA55AA;
		asm("": : :"memory");
		if( *mem != 0x55AA55AA ) {
			memCount = 0;
		} else {
			*mem = 0xAA55AA55;
			asm( "": : :"memory" );
			if( *mem != 0xAA55AA55 ) {
				memCount = 0;
			}
		}
		asm( "": : :"memory" );
		*mem = tempMemory;
	}
	asm( "nop" );
	//MrOlsen (2016-01-10) NOTE: I don't like this but I start incrementing form the start.
	memKb--;
	asm( "nop" );
	load_cr0( cr0 );
  /*
   asm volatile (
   "movl %%ebx, %%cr0\n"
   :
   : "a" (cr0)
   : "ebx"
   );
   */
	asm("nop");
	//	Restore States For Both IRQ 1 And 2
	outportByte( 0x21, irq1State );
	outportByte( 0xA1, irq2State );
	asm( "nop" );
	//	Return Amount Of Memory In Pages
	return( ( memKb * 1024 * 1024 ) / PAGE_SIZE );
}
/************************************************************************
 Function: uInt32 vmm_findFreePage(pid_t pid);
 Description: This Returns A Free  Physical Page Address Then Marks It
 Not Available As Well As Setting The PID To The Proccess
 Allocating This Page
 Notes:
 ************************************************************************/
uint32_t vmm_findFreePage( pidType pid ) {
	int i	= 0x0;
	//	Lets Look For A Free Page
	if( pid < sysID )
		kpanic( "Error: invalid PID %i\n", pid );
	spinLock( &vmmSpinLock );
	for( i = 0; i <= numPages; i++ ) {
		/*
		 * If We Found A Free Page Set It To Not Available After That Set Its Own
		 * And Return The Address
		 */
		if( ( vmmMemoryMap[i].status == memAvail ) && ( vmmMemoryMap[i].cowCounter == 0 ) ) {
			vmmMemoryMap[i].status	= memNotavail;
			vmmMemoryMap[i].pid		= pid;
			freePages--;
			if( systemVitals )
				systemVitals->freePages = freePages;
			spinUnlock( &vmmSpinLock );
			return( vmmMemoryMap[i].pageAddr );
		}
	}
	//	If No Free Memory Is Found Return NULL
	kpanic( "Out Of Memory!!!!" );
	return( 0x0 );
}
/************************************************************************
 Function: int freePage(uInt32 pageAddr);
 Description: This Function Marks The Page As Free
 Notes:
 ************************************************************************/
int freePage( uint32_t pageAddr ) {
	int pageIndex	= 0x0;
	assert( ( pageAddr & 0xFFF ) == 0x0 );
	//	Find The Page Index To The Memory Map
	pageIndex = ( pageAddr / PAGE_SIZE );
	//	Check If Page COW Is Greater Then 0 If It Is Dec It If Not Free It
	if( vmmMemoryMap[pageIndex].cowCounter == 0 ) {
		//	Set Page As Avail So It Can Be Used Again
		spinLock( &vmmSpinLock );
		vmmMemoryMap[pageIndex].status		= memAvail;
		vmmMemoryMap[pageIndex].cowCounter	= 0x0;
		vmmMemoryMap[pageIndex].pid			= -2;
		freePages++;
		systemVitals->freePages = freePages;
		spinUnlock( &vmmSpinLock );
	} else {
		/* Adjust The COW Counter */
		adjustCowCounter( ( ( uint32_t ) vmmMemoryMap[pageIndex].pageAddr ), -1 );
	}
	//	Return
	return(0x0);
}
/************************************************************************
 Function: int adjustCowCounter(uInt32 baseAddr,int adjustment);
 Description: This Adjust The COW Counter For Page At baseAddr It Will
 Error If The Count Goes Below 0
 Notes:
 08/01/02 - I Think If Counter Gets To 0 I Should Free The Page
 ************************************************************************/
int adjustCowCounter( uInt32 baseAddr, int adjustment ) {
	int vmmMemoryMapIndex = ( baseAddr / PAGE_SIZE );
	assert( ( baseAddr & 0xFFF ) == 0x0 );
	spinLock( &vmmSpinLock );
	//	Adjust COW Counter
	vmmMemoryMap[vmmMemoryMapIndex].cowCounter	+= adjustment;
	if( vmmMemoryMap[vmmMemoryMapIndex].cowCounter <= 0 ) {
		if( vmmMemoryMap[vmmMemoryMapIndex].cowCounter < 0 )
			kprintf( "ERROR: Why is COW less than 0" );
		vmmMemoryMap[vmmMemoryMapIndex].cowCounter	= 0x0;
		vmmMemoryMap[vmmMemoryMapIndex].pid			= vmmID;
		vmmMemoryMap[vmmMemoryMapIndex].status		= memAvail;
		freePages++;
		systemVitals->freePages						= freePages;
	}
	spinUnlock( &vmmSpinLock );
	//	Return
	return(0x0);
}
/************************************************************************
 Function: void vmm_freeProcessPages(pid_t pid);
 Description: This Function Will Free Up Memory For The Exiting Process
 Notes:
 08/04/02 - Added Checking For COW Pages First
 ************************************************************************/
/* TODO: This can be greatly improved for performance but it gets the job done */
void vmm_freeProcessPages( pidType pid ) {
	int i					= 0;
	int x					= 0;
	uint32_t *tmpPageTable	= 0x0;
	uint32_t *tmpPageDir	= ( uInt32 * ) PD_BASE_ADDR;
	spinLock( &vmmSpinLock );
	//	Check Page Directory For An Avail Page Table
	//	NOTE: This cleans all memory space up to kernel space
	#ifdef _IGNORE
	for( i = 0; i < ( PAGE_SIZE - ( PAGE_SIZE / 4 ) ); i++ ) {
		if( tmpPageDir[i] != 0 ) {
			//	Set Up Page Table Pointer
			tmpPageTable = ( uint32_t * ) ( PT_BASE_ADDR + ( i * PAGE_SIZE ) );
			//	Check The Page Table For COW Pages
			for( x = 0; x < PD_ENTRIES; x++ ) {
				//	If The Page Is COW Adjust COW Counter
				if( ( ( uint32_t ) tmpPageTable[x] & PAGE_COW ) == PAGE_COW ) {
					adjustCowCounter( ( ( uint32_t ) tmpPageTable[x] & 0xFFFFF000 ), -1 );
				}
			}
		}
	}
	#endif
	//	Loop Through Pages To Find Pages Owned By Process
	for( i = 0; i < numPages; i++ ) {
		if( vmmMemoryMap[i].pid == pid ) {
			//	Check To See If The cowCounter Is Zero If So We Can Ree It
			if( vmmMemoryMap[i].cowCounter == 0 ) {
				vmmMemoryMap[i].status		= memAvail;
				vmmMemoryMap[i].cowCounter	= 0x0;
				vmmMemoryMap[i].pid			= vmmID;
				freePages++;
				systemVitals->freePages		= freePages;
			} else {
				spinUnlock( &vmmSpinLock );
				adjustCowCounter( ( i * PAGE_SIZE ), -1 );
				spinLock( &vmmSpinLock );
			}
		}
	}
	//	Return
	spinUnlock( &vmmSpinLock );
	return;
}