/*
 * (cm_mm.h)- memory manager
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 
#define CM_MM_C


#include <linux/kernel.h>
#include <linux/malloc.h>
#include <net/cm_types.h>
#include <net/cm_mm.h>
#include <net/stk_rsrc.h>
#include <net/llc_dbg.h>

#ifdef CM_MM_DBG
  #define  DBG_MSG(body) { printk body; }
#else
  #define  DBG_MSG(body)  ;
#endif

/* data structures and types */

typedef struct {
	us16 size; 
	memory_pool_t *pool_list;   
} memory_pool_mgr_t;


/* global static data */

static us32 Module_init = NO;
static mph_t Memory_pool_handle;
static memory_pool_mgr_t Memory_pool_mgr;   


/* global static function prototypes */

static us16        verify_pool_handle (mph_t h, memory_pool_t **prev_pool);

/*
 * Function: mm_init
 *
 * Description:
 *  Initializes memory pool manager (Memory_pool_mgr).
 * 
 * Parameters:
 *  None
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_init ()
{
	us16 rc;


	if (Module_init == YES) {
		return (0);
	}

	Module_init = YES;

	Memory_pool_mgr.size = 0;
	Memory_pool_mgr.pool_list = NULLPTR;

	rc = mm_get_pool (&Memory_pool_handle, (us32) MM_MEM_POOL_SIZE);

	if (rc) {
		Module_init = NO;
	}

	return (rc);
}


/*
 * Function: mm_exit
 *
 * Description:
 *  Releases all allocated memories registered in Memory_pool_mgr and 
 *  initializes Memory_pool_mgr.
 * 
 * Parameters:
 *  None
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_exit ()
{
	us16 done_flag = NO;
	memory_pool_t *curr_pool;     
	memory_pool_t *next_pool;

	if (Module_init == NO) {
		return (0);
	}

	for (curr_pool = Memory_pool_mgr.pool_list; done_flag == NO;
		curr_pool = next_pool) {
		
		if (!(curr_pool->attributes & POOL_ATTR_SUB_POOL)) {
			next_pool = curr_pool->next;
			mm_rtn_pool ((mph_t) curr_pool);
		} else {
			next_pool = curr_pool->next;
		}

		if (!next_pool) {
			done_flag = YES;
		}
	}
	Memory_pool_mgr.size = 0;
	Memory_pool_mgr.pool_list = NULLPTR;
	Module_init = NO;

	return (0);    
}


/*
 * Function: mm_get_pool
 *
 * Description:
 *  Allocates size_kb KB of memory by kmalloc(GFP_KERNEL) and initializes 
 *  Memory_pool_t at the head, and adds the pool to Memory_pool_mgr, then
 *  returns pool handle in "h" parameter.
 * 
 * Parameters:
 *  mph_t *h : Memory pool handle returns in this argument. 
 *  us32 size_k_bytes : This argument is size of allocated memory in KB.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_get_pool (mph_t *h, us32 size_k_bytes)
{
	us16 rc; 
	memory_pool_t *tmp;

	if (Module_init == NO) {
		return (1);
	}
	size_k_bytes *= BYTES_1K;

	tmp = (memory_pool_t *) kmalloc((size_k_bytes + 
					sizeof(memory_pool_t)), GFP_KERNEL);
	if (tmp) {
		memset (tmp, 0, (size_k_bytes + sizeof(memory_pool_t)));

		tmp->memory = (us8 *) tmp + sizeof (memory_pool_t);
		tmp->size = size_k_bytes;
		tmp->still_avail = size_k_bytes;
		tmp->next_free = tmp->memory;
		tmp->attributes = 0;   

		tmp->next = Memory_pool_mgr.pool_list;
		Memory_pool_mgr.pool_list = tmp;
		Memory_pool_mgr.size++;
 
		*h = (mph_t) tmp;
		rc = 0;
	} else {
		FDBG_ERR_MSG((KERN_ERR "\n mm_get_pool : failed with size %ldK \n",
		             size_k_bytes/BYTES_1K));
		rc = 1;
	}

	return (rc);
}


/*
 * Function: mm_get_subpool
 *
 * Description:
 *  Allocates "size_kb" in "size_type" of memory pool "h" and sets a 
 *  memory_pool_t for it.
 *  It adds the memory_pool_t to Memory_pool_mgr. An attribute bit in 
 *  memory_pool_t is set to differ it from a real memory pool. It returns the
 *  address of subpool in "sph".
 * 
 * Parameters:
 *  mph_t h : This is handle of memory pool.
 *  mph_t *sph : This will be handle of memory subpool.
 *  us32 size : This is size of subpool in size_type.
 *  us8 size_type : This argument determine type of size (K_BYTE,...).
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_get_subpool (mph_t h, mph_t *sph, us32 size, us8 size_type)
{
	us16 rc = 1;
	memory_pool_t *tmp;

	if (Module_init == NO) {
		return (1);
	}
  
	if (size_type == K_BYTE) {
		size *= BYTES_1K;
	}

	tmp = (memory_pool_t *) mm_malloc (h, (size + sizeof(memory_pool_t)));

	if (tmp) {
		memset (tmp, 0, (size + sizeof(memory_pool_t)));

		tmp->memory = (us8 *) tmp + sizeof (memory_pool_t);
		tmp->size = size;
		tmp->still_avail = size;
		tmp->next_free = tmp->memory;
		tmp->attributes = POOL_ATTR_SUB_POOL;
       
		tmp->next = Memory_pool_mgr.pool_list;
		Memory_pool_mgr.pool_list = tmp;
		Memory_pool_mgr.size++;

		*sph = (mph_t) tmp;

		rc = 0;
	} else {
		FDBG_ERR_MSG((KERN_ERR "\nmm_get_subpool : failed with size 
			%ldK,from mp_handle %p\n", size,(memory_pool_t *)h));
	}
	return (rc);
}


/*
 * Function: mm_rtn_pool
 *
 * Description:
 *  Removes a memory_pool_t pointed by "h" and from Memory_pool_mgr and
 *  frees its memory if it is not a subpool.
 * 
 * Parameters:
 *  mph_t h : This argument is handle of memory pool.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_rtn_pool (mph_t h)
{
	us16 rc = 1;
	memory_pool_t * mp;
	memory_pool_t * prev_mp;

	if (Module_init == NO) {
		return (rc);
	}

	rc = verify_pool_handle (h, NULLPTR);
	if (!rc) {
		rc = mm_clr_pool (h, NO);
		if (!rc) {
			mp = (memory_pool_t *) h;
			verify_pool_handle (h, &prev_mp);
			if (mp == Memory_pool_mgr.pool_list) {
				Memory_pool_mgr.pool_list = mp->next;
			} else {
				prev_mp->next = mp->next;
			}                                         

			Memory_pool_mgr.size--;

			if (!(mp->attributes & POOL_ATTR_SUB_POOL)) {
				kfree (mp);
			}

			rc = 0;
		}
	}
	if (rc){ 
		FDBG_ERR_MSG((KERN_ERR "\nmm_rtn_pool: failed with handle : 
				%p\n", (memory_pool_t *)h));
	}
	return (rc);
}


/*
 * Function: mm_clr_pool
 *
 * Description:
 *  Removes all subpools of pool pointed by "h" from Memory_pool_mgr, 
 *  adjusts memory_pool_t of "h" to cleared state, and memsets memory to
 *  zeros if "zero_out_memory_f" is YES.
 * 
 * Parameters:
 *  mph_t h : This argument is handle of memory pool.
 *  flag_yn zero_out_memory_f : if this flag is set to YES memsets memory 
 *  	to zero.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_clr_pool (mph_t h, flag_yn zero_out_memory_f)
{
 	us16 rc; 
	us32 start;
	us32 end;

	memory_pool_t *curr_pool;
	memory_pool_t *prev_pool;
	memory_pool_t *mp;

	if (Module_init == NO) {
		return (1);
	}

	rc = verify_pool_handle (h, NULLPTR);
	if (!rc) {
		mp = (memory_pool_t *) h;
		start = (us32) h;
		end = start + mp->size;
		for (curr_pool = Memory_pool_mgr.pool_list, prev_pool = NULL;
			curr_pool; curr_pool = curr_pool->next) {

			if ((((us32) curr_pool) > start) && 
					(((us32) curr_pool) < end)) {

				if (!prev_pool) {
					Memory_pool_mgr.pool_list = 
							curr_pool->next;
					Memory_pool_mgr.size--;
				} else {
				
					prev_pool->next = curr_pool = 
							curr_pool->next;
					Memory_pool_mgr.size--;
				}
			} else {
				prev_pool = curr_pool;
			}
		}
		mp->still_avail = mp->size;    
		mp->next_free = mp->memory;  

		if (zero_out_memory_f == YES) {
			memset (mp->memory, 0, mp->size);
		}
       
		rc = 0;
	}

	if (rc){ 
		FDBG_ERR_MSG((KERN_ERR "\nmm_clr_pool: failed with handle : %p
					\n", (memory_pool_t *)h));
	}
	return (rc);
}


/*
 * Function: mm_malloc
 *
 * Description:
 *  Allocates size bytes from memory pool pointed by "h" and returns the 
 *  address. It adjusts entries memory_pool_t of the pool.
 * 
 * Parameters:
 *  mph_t h : This argument is handle of memory pool.
 *  us32 size : This argument is size of memory which must be allocated from
 *  	memory pool.
 *
 * Returns:
 *  A pointer to allocated memory : success
 *  NULLPTR : failure
 */

us8 * 
mm_malloc (mph_t h, us32 size)
{
	us8 *rtn_ptr = NULLPTR;
	us16 rc;
	memory_pool_t *mp;

	if (Module_init == NO) {
		return (NULLPTR);
	}

	rc = verify_pool_handle (h, NULLPTR);
	if (!rc) {
		mp = (memory_pool_t *) h;

		if (size & 1) {
			size++;
		}

		if (((us32) mp->next_free) & 1) {
			mp->next_free++;
			mp->still_avail--;
		}

		if (size <= mp->still_avail) {
			rtn_ptr = mp->next_free;
			mp->still_avail -= size;
			mp->next_free += size;
		}
	}

	if (!rtn_ptr) { 
		FDBG_ERR_MSG((KERN_ERR "\nmm_malloc: failed with handle : %p,
				size :%ld\n", (memory_pool_t *)h,size));
	}
	return (rtn_ptr);
}


/*
 * Function: mm_query_pool
 *
 * Description:
 *  Returns available memory in memory_pool_t pointed by "h" in memory_avail.
 * 
 * Parameters:
 *  mph_t h : This argument is handle of memory pool.
 *  us32 *memory_avail : This argument is used for returning available memory 
 *  in memory pool.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

us16 
mm_query_pool (mph_t h, us32 *memory_avail)
{
	us16 rc;
	memory_pool_t *mp;

	if (Module_init == NO) {
		return (1);
	}

	rc = verify_pool_handle (h, NULLPTR);
	if (!rc) {
		mp = (memory_pool_t *) h;
		*memory_avail = mp->still_avail;
	}

	if (rc) { 
		FDBG_ERR_MSG((KERN_ERR "\nmm_query_pool: failed(invalid handle) 
			with handle : %p,mem_avail=%ld\n", (memory_pool_t *)h,
			((memory_pool_t *)h)->still_avail));
	}
	return (rc);
}


/*
 * Function: verify_pool_handle
 *
 * Description:
 *  Searches list of created memory pool(s); to be valid the handle must be 
 *  equal to the address of a memory pool; if not, then invalid; optionally 
 *  return the handle to the memory pool which just proceeds the located pool 
 *  in the memory pool list.
 * 
 * Parameters:
 *  mph_t h : This argument is handle of memory pool.
 *  memory_pool_t **prev_mp : This argument points to the memory just proceeds
 *  	the located pool in the memory pool list.
 *
 * Returns:
 *  0 : success
 *  1 : failure
 */

static us16 
verify_pool_handle (mph_t h, memory_pool_t **prev_mp)
{
	us16 rc = 1;

	memory_pool_t *pool_list;
	memory_pool_t *mp;
	memory_pool_t *prev_pool;

	prev_pool = NULLPTR;
	mp = (memory_pool_t *) h;

	for (pool_list = Memory_pool_mgr.pool_list ; pool_list && rc;
			prev_pool = pool_list, pool_list = pool_list->next) {
		if (pool_list == mp) {
			rc = 0;
           
			if (prev_mp && *prev_mp) {
				*prev_mp = prev_pool; 
			}
			break;
		}
	}

	if (rc) { 
		FDBG_ERR_MSG((KERN_ERR "\nverify_pool_handle: failed(invalid 
			handle) with handle : %p\n", (memory_pool_t *)h));
	}
	return (rc);
}
