// mm.c
// 09/01/03 Josh Holtrop

// 0x368000 is first available byte (this is right after the kernel @ 1mb and the initrd @ 2mb, 1440kb.


//Pointer to the first pageblock in the linked list
pageblock *first_pageblock = (pageblock *) 0;
//The total amount of physical memory available (bytes)
dword	mm_totalmem = 0x100000;				//start counting from 1mb
//The highest physical address
dword	mm_highestAddress = 0;

//This function initializes the memory manager's linked list, filling it with the
// memory areas returned by bios interrupt 0x8E20
void	mm_init()
{
	dword	*memmap_entries = (dword *) 0x9040A;
	memmap_entry	*maps = (memmap_entry *) 0x92000;
	dword a;
	for (a=0;a<(*memmap_entries);a++)
	{
		if (maps[a].attributes == 1)		// (1) mem free to OS
		{
			mm_totalmem += maps[a].limit.lowdword;
			if (mm_highestAddress < (maps[a].base.lowdword + maps[a].limit.lowdword))
				mm_highestAddress = maps[a].base.lowdword + maps[a].limit.lowdword;
			if  ((maps[a].base.lowdword + maps[a].limit.lowdword) > (FREERAM_START+8192))	//goes past where we start freeram
			{
				if (maps[a].base.lowdword < FREERAM_START)
				{
					maps[a].limit.lowdword = maps[a].limit.lowdword - (FREERAM_START - maps[a].base.lowdword);
					maps[a].base.lowdword = FREERAM_START;	//block at least 4kb, starts >= FREERAM_START
				}
				if (first_pageblock == 0)	//no pageblock page set up yet, so set it up here
				{
					first_pageblock = (pageblock *) maps[a].base.lowdword;
					maps[a].base.lowdword += 4096;
					maps[a].limit.lowdword -= 4096;
					mm_init_pageblockpage(first_pageblock);
					first_pageblock->base = maps[a].base.lowdword;
					first_pageblock->length = maps[a].limit.lowdword / 4096;
					first_pageblock->pid = MM_PB_AVAIL;
				}
				else				//first_pageblock already set up, add on segment
				{
					pageblock *pb = first_pageblock;
					for (;;)
					{
						if (pb->pid == MM_PB_NP)
							break;
						else
							pb++;
					}
					pb->base = maps[a].base.lowdword;
					pb->length = maps[a].limit.lowdword / 4096;
					pb->pid = MM_PB_AVAIL;
				}
			}
		}
	}
	if (first_pageblock == 0)
	{
		printf("ERROR! NO INITIAL PAGE BLOCK CREATED.");
		asm("cli");
		asm("hlt");
		for (;;)
		;
	}
}



//This function initializes a pageblock to be full of empty pointers
void	mm_init_pageblockpage(pageblock *pbp)
{
	int a;
	for (a=0; a<256; a++)       				// 256 pageblock entries * 16 bytes per page block entry = 4096 bytes (1 page of pageblock entries)
	{
		pbp[a].base = 0;
		pbp[a].length = 0;
		pbp[a].pid = MM_PB_NP;
		if (a<255)
			pbp[a].link = (dword)(&pbp[a+1]);
		else
			pbp[a].link = 0;
	}
}


//This function allocates a certain number of physical pages for a particular process
//Returns:
// 0 - no free mem, invalied PID
// void* - physical address of allocated pages
void   *mm_palloc(dword numpages, dword proc_pid)
{
 	if (proc_pid < PID_KERNEL)
 		return 0;
	pageblock *pb = first_pageblock;
	if (mm_freeentries() < 2)
	{
		if(mm_new_pageblock_page() == 0)
			return 0;
	}
	pageblock *newentry = mm_nextpageblockentry();
	for (;;)
	{
		if ((pb->pid == MM_PB_AVAIL) && (pb->length >= numpages))
			break;
		if (pb->link == 0)
			return 0;
		pb = (pageblock *)pb->link;
	}
	if (pb->length == numpages)		//dont need a new entry, just mark this one as used :)
	{
		pb->pid = proc_pid;
		return (void *)pb->base;
	}
	else					//subtract out allocated number of pages from length
	{
		newentry->base = pb->base;
		newentry->length = numpages;
		newentry->pid = proc_pid;
		pb->base += (numpages * 4096);
		pb->length -= numpages;
		return (void *)newentry->base;
	}
}


//This method returns the number of free pageblock entries at the end of the linked list
dword	mm_freeentries()
{
	pageblock *pb = first_pageblock;
	dword counter = 0;
	for (;;)
	{
		if (pb->pid == MM_PB_NP)
			counter++;
		if (pb->link == 0)
			return counter;
		pb = (pageblock *) pb->link;
	}
}


//This method creates a new page used to hold more pageblock entries at the end of the linked list
//Returns:
// 0 - no free pages
// pageblock* - address of pageblock page
pageblock *mm_new_pageblock_page()  	//as of 09/26/03 this method leaks 4kb main memory per call, unrecoverable
{
	pageblock *pb = first_pageblock;
	for (;;)
	{
		if ((pb->pid == MM_PB_AVAIL) && (pb->length > 1))
		{
			pageblock *retval = (pageblock *)pb->base;
			pb->base += 4096;
			pb->length--;
			mm_init_pageblockpage(retval);			//zeros out, links new pageblock page
			mm_lastpageblockentry()->link = (dword)retval;	//set up link to new pageblock page
			return retval;
		}
		pb = (pageblock *) pb->link;
		if (pb == 0)
			return 0;
	}
}


//This method frees a physical page
//Returns:
// 2 - pointer to free was a null pointer
// 1 - address not found
// 0 - all went well
int	mm_pfree(void *ptr)
{
 	if (ptr == 0)
 		return 2;
	pageblock *pb = first_pageblock;
	dword tofree = (dword) ptr;
	for (;;)
	{
		if (pb->base == tofree)		// && (pb->pid == MM_PB_USED))
		{
			pb->pid = MM_PB_AVAIL;		//found block, mark available / coalesce it
			mm_coalesce(pb);
			return 0;
		}
		if (pb->link == 0)
			return 1;
		pb = (pageblock *) pb->link;
	}
}


//This method returns a pointer to the last pageblock in the linked list
pageblock *mm_lastpageblockentry()
{
	pageblock *pb = first_pageblock;
	for (;;)
	{
		if (pb->link == 0)
			return pb;
		pb = (pageblock *)pb->link;
	}
}


//This method returns the next available-for-use pageblock entry
// or 0, if they are all used
pageblock *mm_nextpageblockentry()
{
	pageblock *pb = first_pageblock;
	for (;;)
	{
		if (pb->pid == MM_PB_NP)
			return pb;
		if (pb->link == 0)
			return 0;
		pb = (pageblock *)pb->link;
	}
}


//This method returns the amount of free physical RAM
dword mm_freemem()
{
	dword amount = 0;
	pageblock *pb = first_pageblock;
	for (;;)
	{
		if (pb->pid == MM_PB_AVAIL)
			amount += (pb->length)*4096;
		if (pb->link == 0)
			return amount;
		pb = (pageblock *) pb->link;
	}
}


//This method "walks" through the linked list of pageblock entries and
// combines any two entries that are adjacent and of the same type
void	mm_coalesce(pageblock *pb)
{
	pageblock *pbc = first_pageblock;
	for (;;)
	{
		if (pbc->pid == pb->pid)	//pid fields must match, both same process, both avail, or both not present
		{
			if ((pbc->base + (pbc->length * 4096)) == pb->base)	//pbc ends where pb starts
			{
				pbc->length += pb->length;
				pb->pid = MM_PB_NP;
				mm_coalesce(pbc);			//recursion: checks for any more page blocks to coalesce if one found
				return;					//exit this recursion...
			}
			if ((pb->base + (pb->length * 4096)) == pbc->base)	//pb ends where pbc starts
			{
				pb->length += pbc->length;
				pbc->pid = MM_PB_NP;
				mm_coalesce(pb);
				return;
			}
		}
		if (pbc->link == 0)
			return;
		pbc = (pageblock *)pbc->link;
	}
}











