/*
 * kmem.c - kernel memory allocator implementation.
 *
 * Authors  Ghassan Almaless (2008,2009,2010,2011,2012)
 *          Alain Greiner (2016,2017,2018)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <kernel_config.h>
#include <hal_kernel_types.h>
#include <hal_special.h>
#include <printk.h>
#include <busylock.h>
#include <memcpy.h>
#include <khm.h>
#include <ppm.h>
#include <page.h>
#include <cluster.h>
#include <thread.h>
#include <process.h>
#include <chdev.h>
#include <mapper.h>
#include <vfs.h>
#include <fatfs.h>
#include <ramfs.h>
#include <user_dir.h>
#include <remote_sem.h>
#include <remote_barrier.h>
#include <remote_mutex.h>
#include <remote_condvar.h>
#include <mapper.h>
#include <grdxt.h>
#include <vseg.h>
#include <kmem.h>

/////////////////////////////////
void kmem_print_kcm_table( void )
{
	uint32_t    index;
	kcm_t     * kcm;
	cluster_t * cluster = LOCAL_CLUSTER;

	printk("\n    *** KCM Pointers Table ***\n");

	for( index = 0 ; index < KMEM_TYPES_NR ; index++ )
	{
		kcm = cluster->kcm_tbl[index];
		if( kcm != NULL )
		{
			if( index == kcm->type )
			{
				printk("     - KCM[%s] (at address %x) is OK\n",
				       kmem_type_str( index ) , (intptr_t)kcm );
			}
			else
			{
				printk("     - KCM[%s] (at address %x) is KO : has type %s\n",
				       kmem_type_str( index ) , (intptr_t)kcm , kmem_type_str( kcm->type ) );
			}
		}
	}
}

/////////////////////////////////////////
uint32_t  kmem_type_size( uint32_t type )
{
    if     ( type == KMEM_PAGE )          return CONFIG_PPM_PAGE_SIZE;
    else if( type == KMEM_GENERIC )       return 0;
    else if( type == KMEM_KCM )           return sizeof( kcm_t );
    else if( type == KMEM_VSEG )          return sizeof( vseg_t );
    else if( type == KMEM_DEVICE )        return sizeof( chdev_t );
    else if( type == KMEM_MAPPER )        return sizeof( mapper_t );
    else if( type == KMEM_PROCESS )       return sizeof( process_t );
    else if( type == KMEM_CPU_CTX )       return CONFIG_CPU_CTX_SIZE;
    else if( type == KMEM_FPU_CTX )       return CONFIG_FPU_CTX_SIZE;
    else if( type == KMEM_BARRIER )       return sizeof( remote_barrier_t );

    else if( type == KMEM_DEVFS_CTX )     return sizeof( fatfs_ctx_t );
    else if( type == KMEM_FATFS_CTX )     return sizeof( fatfs_ctx_t );
    else if( type == KMEM_VFS_CTX )       return sizeof( vfs_ctx_t );
    else if( type == KMEM_VFS_INODE )     return sizeof( vfs_inode_t );
    else if( type == KMEM_VFS_DENTRY )    return sizeof( vfs_dentry_t );
    else if( type == KMEM_VFS_FILE )      return sizeof( vfs_file_t );
    else if( type == KMEM_SEM )           return sizeof( remote_sem_t );
    else if( type == KMEM_CONDVAR )       return sizeof( remote_condvar_t );
    else if( type == KMEM_MUTEX )         return sizeof( remote_mutex_t );
    else if( type == KMEM_DIR )           return sizeof( user_dir_t );

	else if( type == KMEM_512_BYTES )     return 512;

	else                                  return 0;
}

/////////////////////////////////////
char * kmem_type_str( uint32_t type )
{
	if     ( type == KMEM_PAGE )          return "KMEM_PAGE";
	else if( type == KMEM_GENERIC )       return "KMEM_GENERIC";
	else if( type == KMEM_KCM )           return "KMEM_KCM";
	else if( type == KMEM_VSEG )          return "KMEM_VSEG";
	else if( type == KMEM_DEVICE )        return "KMEM_DEVICE";
	else if( type == KMEM_MAPPER )        return "KMEM_MAPPER";
	else if( type == KMEM_PROCESS )       return "KMEM_PROCESS";
	else if( type == KMEM_CPU_CTX )       return "KMEM_CPU_CTX";
	else if( type == KMEM_FPU_CTX )       return "KMEM_FPU_CTX";
	else if( type == KMEM_BARRIER )       return "KMEM_BARRIER";

    else if( type == KMEM_DEVFS_CTX )     return "KMEM_DEVFS_CTX";
    else if( type == KMEM_FATFS_CTX )     return "KMEM_FATFS_CTX";
    else if( type == KMEM_VFS_CTX )       return "KMEM_VFS_CTX";
    else if( type == KMEM_VFS_INODE )     return "KMEM_VFS_INODE";
    else if( type == KMEM_VFS_DENTRY )    return "KMEM_VFS_DENTRY";
    else if( type == KMEM_VFS_FILE )      return "KMEM_VFS_FILE";
    else if( type == KMEM_SEM )           return "KMEM_SEM";
    else if( type == KMEM_CONDVAR )       return "KMEM_CONDVAR";
    else if( type == KMEM_MUTEX )         return "KMEM_MUTEX";
    else if( type == KMEM_DIR )           return "KMEM_DIR";

	else if( type == KMEM_512_BYTES )     return "KMEM_512_BYTES";

	else                                  return "undefined";
}

/////////////////////////////////////////////////////////////////////////////////////////////
// This static function dynamically allocates and initializes a specific KCM allocator.
// It uses the KCM allocator embedded in cluster manager, initialized by cluster_init().
/////////////////////////////////////////////////////////////////////////////////////////////
static error_t kmem_create_kcm( uint32_t type )
{
	kcm_t    * kcm;

	assert( ((type > 1) && (type < KMEM_TYPES_NR) ) , "illegal KCM type" );

#if DEBUG_KMEM
thread_t * this = CURRENT_THREAD;
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread[%x,%x] enter / KCM type %s missing in cluster %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, kmem_type_str( type ), local_cxy, cycle );
#endif

	cluster_t * cluster = LOCAL_CLUSTER;

	// allocate memory for the requested KCM allocator
	// from the KCM allocator embedded in cluster descriptor
	kcm = kcm_alloc( &cluster->kcm );

	if( kcm == NULL )
	{
		printk("\n[ERROR] in %s : failed to create KCM type %d in cluster %x\n",
		       __FUNCTION__ , type , local_cxy );
		return ENOMEM;
	}

	// initialize the new KCM allocator
	kcm_init( kcm , type );

	// register it in the KCM pointers Table
	cluster->kcm_tbl[type] = kcm;

	hal_fence();

#if DEBUG_KMEM
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, cycle );
#endif

	return 0;
}

/////////////////////////////////////
void * kmem_alloc( kmem_req_t * req )
{
	cluster_t * cluster = LOCAL_CLUSTER;

	uint32_t    type;
	uint32_t    flags;
	uint32_t    size;    // ln( pages ) if PPM / bytes if KHM / unused if KCM
	void      * ptr;     // memory buffer if KHM or KCM / page descriptor if PPM

	type  = req->type;
	size  = req->size;
	flags = req->flags;

	assert( (type < KMEM_TYPES_NR) , "illegal KMEM request type" );

#if DEBUG_KMEM
thread_t * this = CURRENT_THREAD;
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread [%x,%x] enter / %s / size %d / cluster %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, 
kmem_type_str( type ), size, local_cxy, cycle );
#endif

	// analyse request type
	if( type == KMEM_PAGE )                        // PPM allocator
	{
		// allocate the number of requested pages
		ptr = (void *)ppm_alloc_pages( size );
		if( ptr == NULL )
		{
			printk("\n[ERROR] in %s : failed for type %d / size %d in cluster %x\n",
			    __FUNCTION__ , type , size , local_cxy );
			return NULL;
		}

		// reset page if requested
		if( flags & AF_ZERO ) page_zero( (page_t *)ptr );

#if DEBUG_KMEM
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread[%x,%x] exit / %d page(s) allocated / ppn %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
1<<size, ppm_page2ppn(XPTR(local_cxy,ptr)), cycle );
#endif

	}
	else if( type == KMEM_GENERIC )                // KHM allocator
	{
		// allocate memory from KHM
		ptr = khm_alloc( &cluster->khm , size );
		if( ptr == NULL )
		{
			printk("\n[ERROR] in %s : failed for type %d / size %d in cluster %x\n",
			    __FUNCTION__ , type , size , local_cxy );
			return NULL;
		}

		// reset memory if requested
		if( flags & AF_ZERO ) memset( ptr , 0 , size );

#if DEBUG_KMEM
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread[%x,%x] exit / type %s allocated / base %x / size %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, 
kmem_type_str( type ), (intptr_t)ptr, size, cycle );
#endif

	}
	else                                           // KCM allocator
	{
		// initialize the KCM allocator if not already done
		if( cluster->kcm_tbl[type] == NULL )
		{
            // get lock protecting local kcm_tbl[] array
			busylock_acquire( &cluster->kcm_lock );

            // create missing KCM
			error_t error = kmem_create_kcm( type );

            // release lock protecting local kcm_tbl[] array
			busylock_release( &cluster->kcm_lock );

			if ( error ) 
            {
                 printk("\n[ERROR] in %s : cannot create KCM type %d in cluster %x\n",
                 __FUNCTION__, type, local_cxy );
                 return NULL;
            }
		}

		// allocate memory from KCM
		ptr = kcm_alloc( cluster->kcm_tbl[type] );
		if( ptr == NULL )
		{
			printk("\n[ERROR] in %s : failed for type %d / size %d in cluster %x\n",
		    __FUNCTION__ , type , size , local_cxy );
			return NULL;
		}

		// reset memory if requested
		if( flags & AF_ZERO ) memset( ptr , 0 , kmem_type_size( type ) );

#if DEBUG_KMEM
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM < cycle )
printk("\n[%s] thread [%x,%x] exit / type %s allocated / base %x / size %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, kmem_type_str(type), (intptr_t)ptr, 
kmem_type_size(type), cycle );
#endif

	}

	return ptr;
}

//////////////////////////////////
void kmem_free( kmem_req_t * req )
{
	if( req->type >= KMEM_TYPES_NR )
	{
		assert( false , "illegal request type\n" );
	}

	switch(req->type)
	{
		case KMEM_PAGE:
			ppm_free_pages( (page_t*)req->ptr );
			return;

		case KMEM_GENERIC:
			khm_free( req->ptr );
			return;

		default:
			kcm_free( req->ptr );
			return;
	}
}

