/*  pmalloc.c
*
*  Written by Kenny Ng Jan 00'
*
*
*/

#include "datatype.h"
#include "dm.h"
#include "mmu.h"

WORD dm_os_init = 0;

#undef DEBUG_GENERAL_ERROR     /* define this for showing general error */
#undef MM_CHECK_RANGE          /* define this for more secure allocation, but slower */
#undef DEBUG_TRACE_PTR          /* define this to trace the pointer size and address */
#undef MM_INIT_MEM              /* define this to init/erase memory when a click is allocated/free */

#ifdef PC_SIM
#ifdef DEBUG
#include <stdio.h>
#endif
#endif

#ifdef PC_SIM
extern BYTE *buffer;
#endif

#define BLOCK_RESERVE_SPACE             (sizeof(struct block_header) + sizeof(struct page_identifier))
/* The following definitions only valid for 4KB page size */
extern const UWORD blocksize[];

extern const struct size_identifier mm_link_default[];

struct size_identifier os_mm_link[MM_MAX_ORDER];

#define NUM_BLOCK(index)    (os_mm_link[index].nclick)
#define BLOCKSIZE(index)    (blocksize[index])
#define AREASIZE(index) (PAGE_SIZE << (os_mm_link[index].npage_need))
#define PAGE_DESC(p)        (struct page_identifier *)(((UWORD)(p)) & PAGE_MASK)
#define BH(p)   ((struct block_header*)(p))
#define FBH(p)   ((struct free_block_header*)(p))

#ifdef PC_SIM
static USHORT mm_used_block = 0;
#endif

void os_mm_init()
{
	int i;
	for(i=0;i<MM_MAX_ORDER-1;i++)
		os_mm_link[i] = mm_link_default[i];
}

void *get_os_pages(WORD a, USHORT *blk)
{
#ifdef PC_SIM
	void *ptr = (buffer + (mm_used_block + a) * PAGE_SIZE);
	if(mm_used_block + a + 1 >= 10)
	{
		printf("\n[M] [Malloc]: No more Page in system ");
		return NULL;
	}
	mm_used_block += ++a;
	return ptr;
#else
	void *ptr;
	if((ptr = MemoryDMANew(DM_OS, a+1, blk)) == NULL)
	{
		printf("\n[M] [Malloc]: No more Page in system ");
		return NULL;
	}
	else
		return ptr;
#endif
}


#ifdef MM_FREE_EXTRA
void free_os_pages(void *pg)
{
#ifdef PC_SIM
	mm_used_block -= mm_link_default[((struct page_identifier *) pg)->npage_order].npage_need + 1;
#else
	USHORT mmu_block_num = ((struct page_identifier *) pg)->block;
	WORD num_page = mm_link_default[((struct page_identifier *) pg)->npage_order].npage_need;
	USHORT next_block;
	
#ifdef DEBUG_TRACE_PTR
	printf("\nPage %d num page = %d: Freeing page ", mmu_block_num, num_page + 1);
#endif
	
	do
	{
#ifdef DEBUG_TRACE_PTR               
		printf("  %d  ", mmu_block_num);
#endif
		if(num_page)
		{
			if( MemoryNextBlock2(mmu_block_num, &next_block) != TRUE )
				next_block = 0xffff;
		}
		MemoryReleaseBlock2(mmu_block_num, FALSE, TRUE);
		mmu_block_num = next_block;
		
	} while(num_page--);
#endif
}
#endif  //MM_FREE_EXTRA

void *pmalloc(WORD size)
{
    WORD order;
    struct free_block_header *p;
    struct page_identifier *page, **pg;
    struct size_identifier *bucket = os_mm_link;
	
#ifdef DEBUG_TRACE_PTR
    printf("\n[PMalloc] request size %d ", size);
#endif
	
    order = 0;	/* calculate the order for the size, including the 4-byte header */
    {
        UWORD real_size = size + sizeof(struct block_header);
        for (;;) {
			UWORD ordersize = BLOCKSIZE(order);
			if (real_size <= ordersize)
				break;
			order++;   /* block size */
            bucket++;  /* os_mm_link */
			if (ordersize) /* == 0 means end */
				continue;
			
            printf("\n[PMalloc] allocate size %d which is larger than max size", size);
			
			return NULL;
		}
    }
	
    pg = (struct page_identifier**)&bucket->pfree_head;   /* pg point to the value of os_mm_link[x].pfree_head */
	
    page = *pg;					/* page point to the first free block */
    
    if (!page)                                  /* os_mm_link[x].pfree_head = NULL */
		goto no_bucket_page;
    
    p = (struct free_block_header*) page->first_free_click; /* p point to a free click in the page */
	
#ifdef MM_CHECK_RANGE
    if( ( (UWORD) p >> 24)  != ( (UWORD)page >> 24 ) )
    {
#ifdef DEBUG_GENERAL_ERROR
        {
			WORD d0=0x3ffffff;
			printf("\n[PMalloc] ********* Wrong free click number for page %08x ", page);
			while(d0--);
        }
#endif
        goto no_bucket_page;
    }
	
    if( ( (UWORD)p - ((UWORD) page + sizeof(struct page_identifier)) ) % blocksize[order])
    {
#ifdef DEBUG_GENERAL_ERROR
        {
			WORD d1 = 0x3ffffff;
			printf("\n[PMalloc] ************** Invalid page boundary for ptr %08x ", p);
			while(d1--);
        }
#endif
        goto no_bucket_page;
    }
#endif  // MM_CHECK_RANGE
	
    if (p->flag != MM_FREE)     /* the click should be free */
        goto not_free_on_freelist;
	
found_it:
    page->first_free_click = (struct block_header*) p->next;  /* update free list pointer in the page header*/
    page->nfree_click--;
    if (!page->nfree_click)     /* total free = 0  ==>  block full */
        *pg = page->next;               /* os_mm_link[x].pfree_head pointer to next linked block */
	
	p->flag = MM_USED;		/* mark the allocated click in used */
	
#ifdef MM_INIT_MEM
    memset(p+1, 0xf0, size);
#endif
	
#ifdef DEBUG_TRACE_PTR
    printf("\n[PMalloc] Alloc ptr %08x ", ((UWORD)p + sizeof(struct block_header)));
#endif
	
    return (void*)((UWORD)p + sizeof(struct block_header));		/* skip header */
    
no_bucket_page:
	/* need to allocate a new page */
    {
        WORD i, sz;
        USHORT mem_block;
        
		sz = BLOCKSIZE(order);	/* calculate request size */
        
        page = get_os_pages(bucket->npage_need, &mem_block);   /* request new page from OS */
		if (!page)								/* no more page */
            goto no_free_os_page;
		
        bucket->npages++;                                               /* os_mm_link[x].npages ++ */
		
		/* init the new block */
		page->npage_order = (USHORT) order;
        page->block = mem_block;
		
		i = (page->nfree_click = bucket->nclick) - 1;
		p = FBH(page + 1);  /* skip block header */
		while (i) {
			i--;
			p->flag = MM_FREE;
            p->next = (struct free_block_header *) ((WORD) p + sz);
			p = p->next;
		}
		
		p->flag = MM_FREE;		/* this is the last block, set next to NULL */
		p->next = NULL;
		p = (struct free_block_header *)(page + 1);
    }
    
    page->next = *pg;  /* append the list to mm_list[x] */
    *pg = page;
    goto found_it;		/* go back and find again */
    
no_free_os_page:
#ifdef DEBUG_GENERAL_ERROR
    printf("\n[PMalloc] No free page");
    return NULL;
#endif
not_free_on_freelist:
#ifdef DEBUG_GENERAL_ERROR
    printf("\n--------------- [PM] No more DM ");
#endif
    return NULL;
}

void qfree(void *pool);
void pfree(void *__ptr)
{
    USHORT order;
    struct page_identifier *page, **pg;
    struct size_identifier *bucket;
    
    if (!__ptr)				/* try to free a NULL pointer */
		goto null_free;
	
    if(__ptr < (void*)0x40000000)
    {
		return qfree(__ptr);
    }
	
#ifdef DEUBG_TRACE_PTR
    printf("\n[PMalloc] Free ptr %08x ", __ptr);
#endif
	
#define ptr ((struct free_block_header *) __ptr)
	
	__ptr = (void*) ((UWORD) __ptr - sizeof(struct block_header));  /* point to click header */
	
	if(ptr->flag != MM_USED)	/* invalid header */
		goto invalid_free;
	
    page = PAGE_DESC(ptr);		/* page point to page header */
    
    order = page->npage_order;
    if (order >= MM_MAX_ORDER)	/* invalid page header */
		goto bad_order;
	
    bucket = os_mm_link + order;       /* point to os_mm_link */
	
	if(page->nfree_click > bucket->nclick)	/* invalid page header */
		goto bad_header;
    
    pg = (struct page_identifier **)&bucket->pfree_head;
    
    ptr->flag = MM_FREE;    /* mark the click free */
	
    ptr->next = (struct free_block_header *) page->first_free_click;		/* append to page header's free list */
    page->first_free_click = (struct block_header *) ptr;
    if (!page->nfree_click++) {				/* this page can only hold 1 page */
		
#ifdef MM_FREE_EXTRA
        if (bucket->nclick == 1)			/* this should always happen */
            goto free_os_page;                                     /* when a large click is free, should release the memory to system */
#endif
		
        page->next = *pg;					/* else, link it to mm_list[x] */
        *pg = page;
    }
	
#ifdef MM_FREE_EXTRA
    if (page->nfree_click == bucket->nclick) {	/* all clicks are free */
		if((bucket->npage_need == 0) && (bucket->npages == 1))
			goto do_not_free;  /* if it only need 1 page, don't free it and let it occupy the memory */
        for (;;) {
            struct page_identifier *tmp = *pg;
            if (!tmp)
                goto not_on_freelist;
            if (tmp == page)
                break;
            pg = &tmp->next;
        }
        *pg = page->next;
free_os_page:
        bucket->npages--;
        free_os_pages(page);
    }
#endif
	return;
	
null_free:
#ifdef DEBUG_GENERAL_ERROR
	return;
#endif
invalid_free:
#ifdef DEBUG_GENERAL_ERROR
	printf("\n[PMalloc] Inv Free");
	return;
#endif
not_on_freelist:
#ifdef DEBUG_GENERAL_ERROR
	printf("\n[PMalloc] Not in Free List");
	return;
#endif
bad_order:
#ifdef DEBUG_GENERAL_ERROR
	printf("\n[PMalloc] Bad order");
	return;
#endif
bad_header:
#ifdef DEBUG_GENERAL_ERROR
	printf("\n[PMalloc] Bad header");
	return;
#endif
do_not_free:
#ifdef DEBUG_GENERAL_ERROR
	printf("\n[PMalloc] Do not free");
#endif
	return;
}


/* pcalloc                                                           */
/* Purpose : allocate memory & set value to NULL                     */
/* Input   : size       byte to allocate                             */
/* Output  : None                                                    */
/* Return  : NULL       if fail                                      */
/*           a pointer to allocated memory if success                */
/* Comment : None                                                    */
void *pcalloc(size_t num, size_t size)
{
    size_t i       = size * num;
    void  *result  = pmalloc(i);
	
    if (result)
    {
		BYTE *cp   = result;
		if (i) do *cp++ = 0; while (--i);
    }
    return result;
}

