#include "etherboot.h"
#include "init.h"
#include "memsizes.h"

size_t heap_ptr, heap_top, heap_bot;

#define _virt_start 0

static void init_heap(void)
{
	size_t size;
	size_t start, end;
	unsigned i;
	/* Find the largest contiguous area of memory that
	 * I can use for the heap, which is organized as 
	 * a stack that grows backwards through memory.
	 */

	/* If I have virtual address that do not equal physical addresses
	 * there is a change I will try to use memory from both sides of
	 * the virtual address space simultaneously, which can cause all kinds
	 * of interesting problems.
	 * Avoid it by logically extending etherboot.  Once I know that relocation
	 * works I can just start the virtual address space at 0, and this problem goes
	 * away so that is probably a better solution.
	 */
#if 0
	start = virt_to_phys(_text);
#else
	/* segment wrap around is nasty don't chance it. */
	start = virt_to_phys(_virt_start);
#endif
	end  = virt_to_phys(_end);
	size = 0;
	for(i = 0; i < meminfo.map_count; i++) {
		unsigned long r_start, r_end;
		if (meminfo.map[i].type != E820_RAM)
			continue;
		if (meminfo.map[i].addr > ULONG_MAX)
			continue;
		if (meminfo.map[i].size > ULONG_MAX)
			continue;
		
		r_start = meminfo.map[i].addr;
		r_end = r_start + meminfo.map[i].size;
		if (r_end < r_start) {
			r_end = ULONG_MAX;
		}
		/* Handle areas that overlap etherboot */
		if ((end > r_start) && (start < r_end)) {
			/* Etherboot completely covers the region */
			if ((start <= r_start) && (end >= r_end))
				continue;
			/* Etherboot is completely contained in the region */
			if ((start > r_start) && (end < r_end)) {
				/* keep the larger piece */
				if ((r_end - end) >= (r_start - start)) {
					r_start = end;
				}
				else {
					r_end = start;
				}
			}
			/* Etherboot covers one end of the region.
			 * Shrink the region.
			 */
			else if (end >= r_end) {
				r_end = start;
			}
			else if (start <= r_start) {
				r_start = end;
			}
		}
		/* If two areas are the size prefer the greater address */
		if (((r_end - r_start) > size) ||
			(((r_end - r_start) == size) && (r_start > heap_top))) {
			size = r_end - r_start;
			heap_top = r_start;
			heap_bot = r_end;
		}
	}
	if (size == 0) {
		printf("init_heap: No heap found.\n");
		exit(1);
	}
	heap_ptr = heap_bot;
}

static void reset_heap(void)
{
	heap_ptr = heap_bot;
}

void *allot(size_t size)
{
	void *ptr;
	size_t *mark, addr;
	/* Get an 16 byte aligned chunk of memory off of the heap 
	 * An extra sizeof(size_t) bytes is allocated to track
	 * the size of the object allocated on the heap.
	 */
	addr = (heap_ptr - (size + sizeof(size_t))) &  ~15;
	if (addr < heap_top) {
		ptr = 0;
	} else {
		mark = phys_to_virt(addr);
		*mark = size;
		heap_ptr = addr;
		ptr = phys_to_virt(addr + sizeof(size_t));
	}
	return ptr;
}

//if mask = 0xf, it will be 16 byte aligned
//if mask = 0xff, it will be 256 byte aligned
//For DMA memory allocation, because it has more reqiurement on alignment
void *allot2(size_t size, uint32_t mask)
{
        void *ptr;
        size_t *mark, addr;
	uint32_t *mark1;
        
	addr = ((heap_ptr - size ) &  ~mask) - sizeof(size_t) - sizeof(uint32_t);
        if (addr < heap_top) {
                ptr = 0;        
        } else {        
                mark = phys_to_virt(addr);
                *mark = size;  
		mark1 = phys_to_virt(addr+sizeof(size_t));
		*mark1 = mask; 
                heap_ptr = addr;
                ptr = phys_to_virt(addr + sizeof(size_t) + sizeof(uint32_t));
        }                       
        return ptr;             
}  

void forget(void *ptr)
{
	size_t *mark, addr;
	size_t size;

	if (!ptr) {
		return;
	}
	addr = virt_to_phys(ptr);
	mark = phys_to_virt(addr - sizeof(size_t));
	size = *mark;
	addr += (size + 15) & ~15;
	
	if (addr > heap_bot) {
		addr = heap_bot;
	}
	heap_ptr = addr;
}

void forget2(void *ptr)
{
        size_t *mark, addr;
        size_t size;
	uint32_t mask;
	uint32_t *mark1;

        if (!ptr) {
                return;
        }
        addr = virt_to_phys(ptr);
        mark = phys_to_virt(addr - sizeof(size_t) - sizeof(uint32_t));
        size = *mark;
	mark1 = phys_to_virt(addr - sizeof(uint32_t));
	mask = *mark1;
        addr += (size + mask) & ~mask;

        if (addr > heap_bot) {
                addr = heap_bot;
        }
        heap_ptr = addr;
}

INIT_FN ( INIT_HEAP, init_heap, reset_heap, NULL );
