/*
 * kmem.c - kernel memory allocator implementation.
 *
 * Authors  Ghassan Almaless (2008,2009,2010,2011,2012)
 *          Mohamed Lamine Karaoui (2015)
 *          Alain Greiner (2016)
 *
 * 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 <almos_config.h>
#include <hal_types.h>
#include <hal_special.h>
#include <printk.h>
#include <spinlock.h>
#include <readlock.h>
#include <memcpy.h>
#include <khm.h>
#include <ppm.h>
#include <page.h>
#include <cluster.h>
#include <thread.h>
#include <process.h>
#include <device.h>
#include <mapper.h>
#include <vfs.h>
#include <fatfs.h>
#include <ramfs.h>
#include <remote_sem.h>
#include <remote_barrier.h>
#include <mapper.h>
#include <grdxt.h>
#include <vseg.h>
#include <kmem.h>

/////////////////////////////////////////////////////////////////////////////////////////////
// This global array is indexed by the Kernel Memory Object Type (defined in kmem.h)
// It contains the size of fixed size objects type dynamically allocated by the KCMs.
// This array should be consistent with the enum defined kmem.h.
/////////////////////////////////////////////////////////////////////////////////////////////

uint32_t  kmem_size_tbl[KMEM_TYPES_NR] =
{
    0,                        // 0  KMEM_PAGE is not a fixed size object
    0,                        // 1  KMEM_GENERIC   
    sizeof( kcm_t ),          // 2  KMEM_KCM
    sizeof( vseg_t ),         // 3  KMEM_VSEG
    sizeof( device_t ),       // 4  KMEM_DEVICE
    sizeof( mapper_t ),       // 5  KMEM_MAPPER
    sizeof( process_t ),      // 6  KMEM_PROCESS
    0,                        // 7 
    0,                        // 8  
    0,                        // 9  

    sizeof( fatfs_inode_t ),  // 10 KMEM_FATFS_INODE
    sizeof( fatfs_ctx_t ),    // 11 KMEM_FATFS_CTX
    sizeof( ramfs_inode_t ),  // 12 KMEM_RAMFS_INODE
    sizeof( ramfs_ctx_t ),    // 13 KMEM_RAMFS_CTX
    sizeof( vfs_ctx_t ),      // 14 KMEM_VFS_CTX
    sizeof( vfs_inode_t ),    // 15 KMEM_VFS_INODE
    sizeof( vfs_dentry_t ),   // 16 KMEM_VFS_DENTRY
    sizeof( vfs_file_t ),     // 17 KMEM_VFS_FILE
    sizeof( remote_sem_t ),   // 18 KMEM_SEM
    0,                        // 19 

    64,                       // 20 KMEM_64_BYTES
    128,                      // 21 KMEM_128_BYTES
    256,                      // 22 KMEM_256_BYTES
    512,                      // 23 KMEM_512_BYTES
    1024,                     // 24 KMEM_1024_BYTES
    2048,                     // 25 KMEM_2048_BYTES
};

/////////////////////////////////////////////////////////////////////////////////////////////
// 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_get_kcm( uint32_t type )
{
	kcm_t    * kcm;
	error_t    error;

    // check kmem object type
    if( (type < 2) || (type >= KMEM_TYPES_NR) )
    {
        printk("\n[PANIC] in %s illegal request type\n", __FUNCTION__ );
        hal_core_sleep();
    }

    cluster_t * cluster = LOCAL_CLUSTER;

    // allocates memory for the requested KCM allocator
	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;
    }

    // initializes the new KCM allocator 
	error = kcm_init( kcm , type );

	if( error )
	{
		printk("\n[ERROR] in %s : failed to init KCM type %d\n",
               __FUNCTION__ , type , local_cxy );
		return error;
	}

	cluster->kcm_tbl[type] = kcm;
	hal_wbflush();

	return 0;
}
 

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

	uint32_t    type;
	uint32_t    size;    // ln( pages ) if PPM / bytes if KHM or BKM / unused if KCM
	uint32_t    flags;
	void      * ptr;     // local pointer on allocated memory buffer

	type  = req->type;
	size  = req->size;
	flags = req->flags;
  
	kmem_dmsg("\n[INFO] %s in cluster %x : type %d, size %d, flags %x at cycle %d \n",
		      __FUNCTION__, local_cxy , type, size, flags , hal_time_stamp() );

	if( type >= KMEM_TYPES_NR )
    {
        printk("\n[PANIC] in %s illegal request type\n", __FUNCTION__ );
        hal_core_sleep();
    }
  
    // analyse request type
	if( type ==  KMEM_PAGE )                       // PPM allocator
    {        
        // allocate the number of requested pages
		ptr = (void*)ppm_alloc_pages( size );

        // reset page if required
		if( flags & AF_ZERO ) page_zero( (page_t*)ptr );
	}
    else if( type == KMEM_GENERIC )                // KHM allocator
    {
        // allocate memory from KHM
		ptr = khm_alloc( &cluster->khm , size );

        // reset memory if requested
		if( flags & AF_ZERO ) memset( ptr , 0 , size );
	}
    else                                           // KCM allocator
    {
        uint32_t error = 0;

        // initialize the KCM allocator if not already done
	    if( cluster->kcm_tbl[type] == NULL )
	    { 
            spinlock_lock( &cluster->kcm_lock );
			error = kmem_get_kcm( type );
            spinlock_unlock( &cluster->kcm_lock );
	    }

        // allocate memory from KCM if success
        if( error ) ptr = NULL;
        else        ptr = kcm_alloc( cluster->kcm_tbl[type] );
	}

    if( ptr == NULL )
    {
	    printk("\n[ERROR] in %s : failed for type %d, size %d, flags %x in cluster %x\n", 
	       __FUNCTION__ , type , size , flags , local_cxy );
 
	    return NULL;
    }

	kmem_dmsg("\n[INFO] %s got ptr = %x in cluster %x at cycle %d\n",
		  __FUNCTION__, (intptr_t)ptr , local_cxy , hal_time_stamp() );
	    
	return ptr;

} // end kmem_alloc()

//////////////////////////////////
void kmem_free( kmem_req_t * req )
{
	if( req->type >= KMEM_TYPES_NR )
    {
        printk("\n[PANIC] in %s : illegal request type\n", __FUNCTION__ );
        hal_core_sleep();
    }
  
	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;
	}
}


