/*
 * Copyright (C) 2001-2004 Jakub Jermar
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <arch/types.h>
#include <func.h>

#include <mm/heap.h>
#include <mm/frame.h>
#include <mm/page.h>
#include <mm/vm.h>
#include <arch/mm/page.h>

#include <config.h>
#include <memstr.h>

#include <panic.h>

#include <synch/spinlock.h>

__u32 frames;
__u32 frames_free;

__u8 *frame_bitmap;
__u32 frame_bitmap_octets;

/*
 * This is for kernel address space frames (allocated with FRAME_KA).
 * Their addresses may not interfere with user address space.
 */
__u8 *frame_kernel_bitmap;
__u32 kernel_frames;
__u32 kernel_frames_free;

static spinlock_t framelock;

void frame_init(void)
{
        if (config.cpu_active == 1) {

                /*
                 * The bootstrap processor will allocate all necessary memory for frame allocation.
                 */

                frames = config.memory_size / FRAME_SIZE;
                frame_bitmap_octets = frames / 8 + (frames % 8 > 0);

                frame_bitmap = (__u8 *) malloc(frame_bitmap_octets);
                if (!frame_bitmap)
                        panic(PANIC "malloc/frame_bitmap\n");

                /*
                 * Mark all frames free.
                 */
                memsetb((__address) frame_bitmap, frame_bitmap_octets, 0);
                frames_free = frames;

		/*
		 * Will be properly set up by architecture dependent frame init.
		 */
		frame_kernel_bitmap = NULL;
		kernel_frames_free = 0;
		kernel_frames = 0;	
	}

	/*
	 * No frame allocations/reservations  prior this point.
	 */

	frame_arch_init();

	if (config.cpu_active == 1) {
                /*
                 * Create the memory address space map. Marked frames and frame
                 * regions cannot be used for allocation.
                 */
		frame_region_not_free(config.base, config.base + config.kernel_size);
	}
}

/*
 * Allocate a frame.
 */
__address frame_alloc(int flags)
{
	int i;
	pri_t pri;
	__u8 **frame_bitmap_ptr = &frame_bitmap;
	__u32 *frames_ptr = &frames, *frames_free_ptr = &frames_free;
	
	if (flags & FRAME_KA) {
		frame_bitmap_ptr = &frame_kernel_bitmap;
		frames_ptr = &kernel_frames;
		frames_free_ptr = &kernel_frames_free;
	}
	
loop:
	pri = cpu_priority_high();
	spinlock_lock(&framelock);
	if (*frames_free_ptr) {
		for (i=0; i < *frames_ptr; i++) {
			int m, n;
		
			m = i / 8;
			n = i % 8;

			if (((*frame_bitmap_ptr)[m] & (1<<n)) == 0) {
				(*frame_bitmap_ptr)[m] |= (1<<n);
				*frames_free_ptr--;
				if (flags & FRAME_KA) {
					/*
					 * frames_free_ptr points to kernel_frames_free
					 * It is still necessary to decrement frames_free.
					 */
					frames_free--;
				}
				spinlock_unlock(&framelock);
				cpu_priority_restore(pri);
				if (flags & FRAME_KA) return PA2KA(i*FRAME_SIZE);
				return i*FRAME_SIZE;
			}
		}
		panic(PANIC "frames_free inconsistent (%d)\n", frames_free);
	}
	spinlock_unlock(&framelock);
	cpu_priority_restore(pri);

	if (flags & FRAME_PANIC)
		panic(PANIC "unable to allocate frame\n");
		
	/* TODO: implement sleeping logic here */
	panic(PANIC "sleep not supported\n");
	
	goto loop;
}

/*
 * Free a frame.
 */
void frame_free(__address addr)
{
	pri_t pri;
	__u32 frame;
	__u32 *frames_free_ptr = &frames_free, *frames_ptr = &frames;
	__u8 **frame_bitmap_ptr = &frame_bitmap;

	if (IS_KA(addr)) {
		frames_free_ptr = &kernel_frames_free;
		frame_bitmap_ptr = &frame_kernel_bitmap;
	}

	pri = cpu_priority_high();
	spinlock_lock(&framelock);
	
	frame = IS_KA(addr) ? KA2PA(addr) : addr;
	frame /= FRAME_SIZE;
	if (frame < *frames_ptr) {
		int m, n;
	
		m = frame / 8;
		n = frame % 8;
	
		if ((*frame_bitmap_ptr)[m] & (1<<n)) {
			(*frame_bitmap_ptr)[m] &= ~(1<<n);
			*frames_free_ptr++;
			if (IS_KA(addr)) {
				/*
				 * frames_free_ptr points to kernel_frames_free
				 * It is still necessary to increment frames_free.
				 */
				 frames_free++;
			}	
		}
		else panic(PANIC "frame_free: frame already free\n");
	}
	else panic(PANIC "frame_free: frame number too big\n");
	
	spinlock_unlock(&framelock);
	cpu_priority_restore(pri);
}

/*
 * Don't use this function for normal allocation. Use frame_alloc() instead.
 * Use this function to declare that some special frame is not free.
 */
void frame_not_free(__address addr)
{
	pri_t pri;
	__u32 frame;
	__u32 *frames_ptr = &frames, *frames_free_ptr = &frames_free;
	__u8 **frame_bitmap_ptr = &frame_bitmap;
	
	pri = cpu_priority_high();
	spinlock_lock(&framelock);
	frame = IS_KA(addr) ? KA2PA(addr) : addr;
	frame /= FRAME_SIZE;
	if (frame < *frames_ptr) {
		int m, n;

		m = frame / 8;
		n = frame % 8;
	
		if (((*frame_bitmap_ptr)[m] & (1<<n)) == 0) {	
			(*frame_bitmap_ptr)[m] |= (1<<n);
			*frames_free_ptr--;	
			if (IS_KA(addr)) {
				/*
				 * frames_free_ptr points to kernel_frames_free
				 * It is still necessary to decrement frames_free.
				 */
				frames_free--;
			}
		}
	}
	spinlock_unlock(&framelock);
	cpu_priority_restore(pri);
}

void frame_region_not_free(__address start, __address stop)
{
	__u32 i;

	start /= FRAME_SIZE;
	stop /= FRAME_SIZE;
	for (i = start; i <= stop; i++)
		frame_not_free(i * FRAME_SIZE);
}
