/*
 * kmem.c - kernel memory allocator implementation.
 *
 * Authors  Alain Greiner (2016,2017,2018,2019,2020)
 *
 * 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 <cluster.h>
#include <thread.h>
#include <memcpy.h>
#include <khm.h>
#include <ppm.h>
#include <kcm.h>
#include <page.h>
#include <kmem.h>

/////////////////////////////////////
void * kmem_alloc( kmem_req_t * req )
{
	uint32_t    type;    // KMEM_PPM / KMEM_KCM / KMEM_KHM
	uint32_t    flags;   // AF_NONE / AF_ZERO / AF_KERNEL
	uint32_t    order;   // PPM: ln(pages) / KCM: ln(bytes) / KHM: bytes

	type  = req->type;
	order = req->order;
	flags = req->flags;

    ////////////////////// 
	if( type == KMEM_PPM )
	{
		// allocate the number of requested pages
		page_t * page_ptr = (void *)ppm_alloc_pages( order );

		if( page_ptr == NULL )
		{
			printk("\n[ERROR] in %s : PPM failed / order %d / cluster %x\n",
			__FUNCTION__ , order , local_cxy );
			return NULL;
		}

        xptr_t page_xp = XPTR( local_cxy , page_ptr );

		// reset page if requested
		if( flags & AF_ZERO ) page_zero( page_ptr );

        // get pointer on buffer from the page descriptor
        void * ptr = GET_PTR( ppm_page2base( page_xp ) );

#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] from PPM / %d page(s) / ppn %x / cxy %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
1<<order, ppm_page2ppn(XPTR(local_cxy,ptr)), local_cxy, cycle );
#endif
        return ptr;
	}
    /////////////////////////// 
	else if( type == KMEM_KCM )
	{
		// allocate memory from KCM
		void * ptr = kcm_alloc( order );

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

		// reset memory if requested
		if( flags & AF_ZERO ) memset( ptr , 0 , 1<<order );

#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] from KCM / %d bytes / base %x / cxy %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
1<<order, ptr, local_cxy, cycle ); 
#endif
        return ptr;
	}
    /////////////////////////// 
	else if( type == KMEM_KHM )
	{
		// allocate memory from KHM
		void * ptr = khm_alloc( &LOCAL_CLUSTER->khm , order );

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

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

#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] from KHM / %d bytes / base %x / cxy %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, 
order, ptr, local_cxy, cycle );
#endif
        return ptr;
	}
    else
    {
        printk("\n[ERROR] in %s : illegal allocator type\n", __FUNCTION__);
        return NULL;
    }
}  // end kmem_alloc()

//////////////////////////////////
void kmem_free( kmem_req_t * req )
{
    uint32_t type = req->type;

    //////////////////////
	if( type == KMEM_PPM )
	{
        page_t * page = GET_PTR( ppm_base2page( XPTR( local_cxy , req->ptr ) ) );

        ppm_free_pages( page );
    }
    ///////////////////////////
    else if( type == KMEM_KCM )
    {
        kcm_free( req->ptr );
	}
    ///////////////////////////
    else if( type == KMEM_KHM )
    {
        khm_free( req->ptr );
    }
    else
    {
        printk("\n[ERROR] in %s : illegal allocator type\n", __FUNCTION__);
    }
}  // end kmem_free()

///////////////////////////////////////////
void * kmem_remote_alloc( cxy_t        cxy,
                          kmem_req_t * req )
{
	uint32_t    type;    // KMEM_PPM / KMEM_KCM / KMEM_KHM
	uint32_t    flags;   // AF_ZERO / AF_KERNEL / AF_NONE
	uint32_t    order;   // PPM: ln(pages) / KCM: ln(bytes) / KHM: bytes

	type  = req->type;
	order = req->order;
	flags = req->flags;

	//////////////////////
	if( type == KMEM_PPM )
	{
		// allocate the number of requested pages from remote cluster
		xptr_t page_xp = ppm_remote_alloc_pages( cxy , order );

		if( page_xp == XPTR_NULL )
		{
			printk("\n[ERROR] in %s : failed for PPM / order %d in cluster %x\n",
			__FUNCTION__ , order , cxy );
			return NULL;
		}

        // get extended pointer on remote buffer
        xptr_t base_xp = ppm_page2base( page_xp );

		// reset page if requested
		if( flags & AF_ZERO ) hal_remote_memset( base_xp , 0 , CONFIG_PPM_PAGE_SIZE );


#if DEBUG_KMEM_REMOTE
thread_t * this = CURRENT_THREAD;
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM_REMOTE < cycle )
printk("\n[%s] thread[%x,%x] from PPM / %d page(s) / ppn %x / cxy %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
1<<order, ppm_page2ppn( page_xp ), cxy, cycle );
#endif
        return GET_PTR( base_xp );
	}
    ///////////////////////////
	else if( type == KMEM_KCM ) 
	{
		// allocate memory from KCM
		void * ptr = kcm_remote_alloc( cxy , order );

		if( ptr == NULL )
		{
			printk("\n[ERROR] in %s : failed for KCM / order %d in cluster %x\n",
		    __FUNCTION__ , order , cxy );
			return NULL;
		}

		// reset memory if requested
		if( flags & AF_ZERO )  hal_remote_memset( XPTR( cxy , ptr ) , 0 , 1<<order );

#if DEBUG_KMEM_REMOTE
thread_t * this = CURRENT_THREAD;
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_KMEM_REMOTE < cycle )
printk("\n[%s] thread [%x,%x] from KCM / %d bytes / base %x / cxy %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
1<<order, ptr, cxy, cycle );
#endif
        return ptr;
	}
	/////////////////////////// 
	else if( type == KMEM_KHM )                
	{
        printk("\n[ERROR] in %s : remote access not supported for KHM\n", __FUNCTION__  );
		return NULL;
	}
    else
    {
        printk("\n[ERROR] in %s : illegal allocator type\n", __FUNCTION__);
        return NULL;
    }
}  // kmem_remote_malloc()

////////////////////////////////////////
void kmem_remote_free( cxy_t        cxy,
                       kmem_req_t * req )
{
    uint32_t type = req->type;

    //////////////////////
	if( type == KMEM_PPM )
	{
        page_t * page = GET_PTR( ppm_base2page( XPTR( cxy , req->ptr ) ) );

        ppm_remote_free_pages( cxy , page );
    }
    ///////////////////////////
    else if( type == KMEM_KCM )
    {
        kcm_remote_free( cxy , req->ptr );
	}
    ///////////////////////////
    else if( type == KMEM_KHM )
    {
        printk("\n[ERROR] in %s : remote access not supported for KHM\n", __FUNCTION__ );
    }
    else
    {
        printk("\n[ERROR] in %s : illegal allocator type\n", __FUNCTION__);
    }
}  // end kmem_remote_free()



