/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2011.

	This file is part of Sortix.

	Sortix 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 3 of the License, or (at your option) any later
	version.

	Sortix 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 Sortix. If not, see <http://www.gnu.org/licenses/>.

	memorymanagement.cpp
	Handles memory for the x86 architecture.

******************************************************************************/

#include "platform.h"
#include "globals.h"
#include "iprintable.h"
#include "log.h"
#include "panic.h"
#include "multiboot.h"
#include "memorymanagement.h"

namespace Sortix
{
	DEFINE_GLOBAL_OBJECT(Paging, Paging);

	const size_t UnallocPageMagic = 0xABBAACDC; // Must this value due to assembly.

	// Creates the Great Linked List of All Linked Lists!
	void Paging::Init(multiboot_info_t* BootInfo)
	{
		uintptr_t KernelStart = 0x000000;
		size_t KernelLength = 0x200000;
		UnallocatedPage = NULL;
		PagesTotal = 0;
		PagesUsed = 0;
		PagesFree = 0;

		if ( !( BootInfo->flags & MULTIBOOT_INFO_MEM_MAP ) )
		{
			Panic("memorymanagement.cpp: The memory map flag was't set in the multiboot structure. Are your bootloader multiboot compliant?");
		}

		for	(
				multiboot_memory_map_t* MMap = (multiboot_memory_map_t*) BootInfo->mmap_addr;
				(uintptr_t) MMap < BootInfo->mmap_addr + BootInfo->mmap_length;
				MMap = (multiboot_memory_map_t *) ((unsigned long) MMap + MMap->size + sizeof(MMap->size))
			)
		{
			if ( MMap->type != 1 ) { continue; }

			// The kernels code may split this into multiple pieces.
			struct { uintptr_t Base; size_t Length; } Entries[2]; nat Num = 1;

			// Attempt to crop the entry so that we only map what we can address.
			Entries[0].Base = (uintptr_t) MMap->addr;
			Entries[0].Length = MMap->len;
#ifdef PLATFORM_X86
			// Figure out if the memory area is addressable (are our pointers big enough?)
			if ( 0xFFFFFFFF < MMap->addr ) { continue; }
			if ( 0xFFFFFFFF < MMap->addr + MMap->len ) { Entries[0].Length = 0xFFFFFFFF - MMap->addr;  }
#endif

			// Detect if this memory is completely covered by the kernel.
			if ( KernelStart <= Entries[0].Base && Entries[0].Base + Entries[0].Length <= KernelStart + KernelLength ) { continue; }

			// Detect if this memory is partially covered by the kernel (from somewhere in the memory to somewhere else in the memory)
			else if ( Entries[0].Base <= KernelStart && KernelStart + KernelLength <= Entries[0].Base + Entries[0].Length )
			{
				Entries[1].Base = KernelStart + KernelLength;
				Entries[1].Length = (Entries[0].Base + Entries[0].Length) - Entries[1].Base;
				Entries[0].Length = KernelStart - Entries[0].Base;
				Num = 2;
			}

			// Detect if this memory is partially covered by the kernel (from the left to somewhere in the memory)
			else if ( Entries[0].Base <= KernelStart + KernelLength && KernelStart + KernelLength <= Entries[0].Base + Entries[0].Length )
			{
				Entries[0].Length = (Entries[0].Base + Entries[0].Length) - (KernelStart + KernelLength);
				Entries[0].Base = KernelStart + KernelLength;
			}

			// Detect if this memory is partially covered by the kernel (from somewhere in the memory to the right)
			else if ( Entries[0].Base <= KernelStart && KernelStart <= Entries[0].Base + Entries[0].Length )
			{
				Entries[0].Length = KernelStart - Entries[0].Base;
			}

			for ( nat I = 0; I < Num; I++ )	
			{
				//GLog->PrintF("Paging: Entry [s:%p, l:%p]\n", Entries[I].Base, Entries[I].Length);

				// Align our entries on page boundaries.
				uintptr_t NewBase = (Entries[I].Base + 0xFFF) / 0x1000 * 0x1000;
				Entries[I].Length = (Entries[I].Base + Entries[I].Length ) - NewBase;
				Entries[I].Length /= 0x1000;
				Entries[I].Base = NewBase;

				if ( Entries[I].Length == 0 ) { continue; }

				UnallocPage* Page = (UnallocPage*) Entries[I].Base;
				Page->Magic = UnallocPageMagic;
				Page->Next = UnallocatedPage;
				Page->ContinuousPages = Entries[I].Length - 1;

				PagesTotal += Entries[I].Length;

				// Debug output, can be safely removed.
				GLog->PrintF("Paging: [s:%p, l:%p]\t->\t[p:0x%p, l:0x%x, n:0x%p]\n", (uint32_t) MMap->addr, (uint32_t) MMap->len, Page, Page->ContinuousPages * 0x1000, Page->Next);

				UnallocatedPage = Page;	
			}
		}

		if ( PagesTotal == 0 ) { Panic("memorymanagement.cpp: no RAM were available for paging"); }

		PagesFree = PagesTotal;

		// Alright, time to make our linked list into a lot of small entries.
		// This speeds up the system when it's fully up and running. It only
		// takes a few miliseconds to run this operation on my laptop.
		GLog->PrintF("Fragmentizing...\n");
		Fragmentize();
		GLog->PrintF("Done!\n");

		GLog->PrintF("%z pages are available for paging (%z MiB RAM)\n", PagesTotal, PagesTotal >> 8 /* * 0x1000 / 1024 / 1024*/);
	}
}
