/**
 * @file malloc.c
 * @brief Simple kernel memory allocator
 * 
 */


#include "malloc.h"
#include "sys.h"
#include "int.h"
#include "debug.h"
#include "io.h"


/**
 * @brief Function to detect the amount of memory
 * @param s - pointer to starting address
 * @return amount of memory
 * 
 * Starts at end of the kernel image and attempts to write a value every 1024 bytes.
 * When it fails to read back the same value as was written, we consider that to be
 * the last address we are able to write to.
 * 
 */
void *
mem_end_detect (void *s)
{
	volatile int *i = (int *) s;

	for (;; i += 1024) {
		*i = 0;
		if (*i != 0)
			break;
		*i = -1;
		if (*i != -1)
			break;
	}

	return (int *) i - 1024;
}


/**
 * @brief Allocate a new free memory block
 * @param size - new memory block size
 * @return pointer to new block or 0 if not enought memory
 * 
 * !!! This routine is NOT thread safe.
 * 
 */
void *
alloc_mem_block (unsigned size)
{
	unsigned *p, s;

	if (size > MAX_BLOCK_SIZE)
		return 0;

	size += 3 * sizeof (unsigned) - 1;
	size /= sizeof (unsigned);
	size += size & 1;

	/* find the end of the kernel binary */
	p = (unsigned *) ((unsigned) &_kernel_end);

	/* loop over blocks */
	while ((s = *p) != 1) {
		/* free and big enough */
		if (!(s & 1) && (s >= size)) {
			/* big exactly */
			if (s <= size + 3) {
				(*p)++;
				return (void *) (p + 2);
			}

			/* make new block */
			*p = size + 1;
			*(p + size) = *(p + s + 1) = s - size;
			*(p + size + 1) = size;
			return (void *) (p + 2);
		}
		p += s & (unsigned) -2;
	}

	/* not enough memory */
	return 0;
}


/**
 * @brief Mark the block as free
 * @param px - pointer to memory block
 *
 * !!! This routine is NOT thread safe.
 * 
 */
void
free_mem_block (void *px)
{
	unsigned *p = (unsigned *) px - 2, *p2;

	/* mark free */
	(*p)--;

	/* merge left (XXX this condition is terrible) */
	if ((*(p + 1) != 0) && (!(*(p2 = (p - *(p + 1))) & 1))) {
		*p2 += *p;
		*(p2 + *p2 + 1) = *p2;
		p = p2;
	}

	/* merge right (XXX this condition is terrible) */
	if (!(*(p2 = (p + *p)) & 1)) {
		*p += *p2;
		*(p2 + *p2 + 1) = *p;
	}
}


/**
 * @brief Initialize the memory block structures
 * 
 */
void
init_memory ()
{
	unsigned *mem_e, *mem_s;

	/* find the end of the kernel binary */
	mem_s = (unsigned *) ((unsigned) &_kernel_end);
	mem_e = mem_end_detect (mem_s);

	/* calculate memory size and write a one big free block */
	*mem_s = (((unsigned) mem_e - (unsigned) mem_s) / sizeof (unsigned) - 2)
		& (unsigned) -2;
	*(mem_s + 1) = 0;

	*(mem_s + *mem_s) = 1;
	*(mem_s + *mem_s + 1) = (unsigned) *mem_s;
}


/**
 * @brief Thread safe memory allocation
 * @param size - new memory block size
 * 
 * This function only improve alloc_mem_block() to be thread save
 * 
 */
void *
malloc (tsize size)
{
	void *ptr;
	save_and_disable_interrupts();
	ptr = alloc_mem_block (size);
	restore_interrupts();
	return ptr;
}


/**
 * @brief Thread safe memory release
 * @param ptr - pointer to memory block
 * 
 * This function only improve free_mem_block() to be thread save
 * 
 */
void
free (void *ptr)
{
	if (ptr) {
		save_and_disable_interrupts();
		free_mem_block (ptr);
		restore_interrupts();
	}
}

/**
 * @brief Call panic when memory leak error
 * 
 */

void memory_leak_error(){
	panic("There is no more memory in the system!!!");
}
