/*
 * kcm.c - Per cluster & per type Kernel Cache Manager access functions
 * 
 * Author  Ghassan Almaless (2008,2009,2010,2011,2012)
 *         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 <list.h>
#include <printk.h>
#include <bits.h>
#include <ppm.h>
#include <thread.h>
#include <page.h>
#include <cluster.h>
#include <kcm.h>

/////////////////////////////////////////////////////////////////////////////////////
// Extern global variable (defined in kmem.c file)
/////////////////////////////////////////////////////////////////////////////////////

extern uint32_t kmem_size_tbl[KMEM_TYPES_NR];


/////////////////////////////////////////////////////////////////////////////////////
// This function allocates one page from local PPM.
/////////////////////////////////////////////////////////////////////////////////////
static page_t * kcm_page_alloc( kcm_t * kcm )
{
	page_t    * page;
    kmem_req_t  req;

    req.type  = KMEM_PAGE;
    req.size  = 0;
    req.flags = AF_KERNEL;
    page = kmem_alloc( &req );
  
	if( page == NULL )
	{
		printk("\n[ERROR] in %s failed to allocate page in cluster %d\n", 
               __FUNCTION__ , local_cxy );
	}

	return page;
}

//////////////////////////////////////////////////////////////////////////////////////
// This static function returns pointer on allocated block from an active page.
// It returns NULL if no block available in selected page.
// It changes the page status if required.
//////////////////////////////////////////////////////////////////////////////////////
static void * get_block( kcm_t      * kcm,
                         kcm_page_t * page )
{
	int32_t index = bitmap_ffs( page->bitmap , sizeof(page->bitmap) ); 

    // the page should be active 
    if( page->active == 0 )
    {
        printk("\n[PANIC] in %s : kcm page should be active\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // the page should not be full
    if( index == -1 )
    {
        printk("\n[PANIC] in %s : kcm page should be active\n", __FUNCTION__ );
        hal_core_sleep();
    }
 
	bitmap_clear( page->bitmap , index );
	page->refcount ++;

    // change the page to busy if last block in page
    if( page->refcount >= kcm->blocks_nr )
    {
        page->active = 0;
		list_unlink( &page->list);
		kcm->active_pages_nr --;

		list_add_first( &kcm->busy_root , &page->list);
		kcm->busy_pages_nr ++;
        page->busy   = 1;
    }

	return (page->blk_tbl + index * kcm->block_size );
}

/////////////////////////////////////////////////////////////////////////////////////
// This static function releases a previously allocated block.
// It changes the page status if required.
/////////////////////////////////////////////////////////////////////////////////////
static void put_block ( kcm_t * kcm,
                        void  * ptr )
{
	kcm_page_t * page;
    uint32_t     index; 
  
	page = (kcm_page_t*)((intptr_t)ptr & CONFIG_PPM_PAGE_MASK);
	index = ((uint8_t*)ptr - page->blk_tbl) / kcm->block_size;
  
	bitmap_set( page->bitmap , index );
	page->refcount --;
  
    // change the page to active if it was busy
	if( page->busy )
	{
		page->busy = 0;
		list_unlink( &page->list );
		kcm->busy_pages_nr --;

		list_add_last( &kcm->active_root, &page->list );
		kcm->active_pages_nr ++;
        page->active = 1;
	}

    // change the page to free if last block in active page
	if( (page->active) && (page->refcount == 0) )
	{
        page->active = 0;
		list_unlink( &page->list);
		kcm->active_pages_nr --;

		list_add_first( &kcm->free_root , &page->list);
		kcm->free_pages_nr ++;
	}
}

/////////////////////////////////////////////////////////////////////////////////////
// This static function tries to allocate one page from PPM. It initializes
// the KCM-page descriptor, and introduces the new page into freelist. 
/////////////////////////////////////////////////////////////////////////////////////
static error_t freelist_populate( kcm_t * kcm )
{
	page_t     * page;
	kcm_page_t * ptr;

    // get one page from local PPM
	page = kcm_page_alloc( kcm );
	if( page == NULL ) return ENOMEM;
 
    // get physical page base address
	ptr = ppm_page2base( page );

    // initialize KCM-page descriptor
	bitmap_set_range( ptr->bitmap , 0 , kcm->blocks_nr );

	ptr->busy          = 0;
	ptr->active        = 0;
	ptr->refcount      = 0;
	ptr->blk_tbl       = (uint8_t*)ptr + kcm->block_size;
	ptr->kcm           = kcm;
	ptr->page          = page;

    // introduce new page in free-list
	list_add_first( &kcm->free_root , &ptr->list );
	kcm->free_pages_nr ++;
 
	return 0;
}

/////////////////////////////////////////////////////////////////////////////////////
// This private function get one KCM page from the KCM freelist.
// It populates the freelist if required.
/////////////////////////////////////////////////////////////////////////////////////
static kcm_page_t * freelist_get( kcm_t * kcm )
{
	error_t      err;
	kcm_page_t * page;

    // get a new page from PPM if freelist empty
	if( kcm->free_pages_nr == 0 )
	{
        err = freelist_populate( kcm );
        if( err	) return NULL;
	}

    // get first KCM page from freelist and change its status to active 
	page = LIST_FIRST( &kcm->free_root, kcm_page_t , list );
	list_unlink( &page->list );
	kcm->free_pages_nr --;
    page->active = 1;

    list_add_first( &kcm->active_root , &page->list);
	kcm->active_pages_nr ++;
    page->active = 1;

	return page;
}


////////////////////////////////////
error_t kcm_init( kcm_t       * kcm,
		          uint32_t      type )
{
	uint32_t     blocks_nr;
	uint32_t     block_size;
	uint32_t     remaining;
	kcm_page_t * page;

	spinlock_init( &kcm->lock );

    // initialize KCM type  
	kcm->type = type;

    // initialise KCM page lists
	kcm->free_pages_nr   = 0;
	kcm->busy_pages_nr   = 0;
	kcm->active_pages_nr = 0;
	list_root_init( &kcm->free_root );
	list_root_init( &kcm->busy_root );
	list_root_init( &kcm->active_root );

    // initialize block size and number of blocks per page,
    // using the global kmem_size_tbl[] array 
	block_size      = ARROUND_UP( kmem_size_tbl[type] , 64 );
	blocks_nr       = CONFIG_PPM_PAGE_SIZE / block_size;
	remaining       = CONFIG_PPM_PAGE_SIZE % block_size;
	blocks_nr       = (remaining >= sizeof(kcm_page_t)) ? blocks_nr : blocks_nr - 1;

	kcm->blocks_nr  = blocks_nr;
	kcm->block_size = block_size;

    // get one page from free_list
	page = freelist_get( kcm );
	if( page == NULL ) return ENOMEM;
     
    // register page in active list
	list_add_first( &kcm->active_root , &page->list );
	page->active = 1;
	kcm->active_pages_nr ++;

	return 0;
}

///////////////////////////////
void kcm_destroy( kcm_t * kcm )
{
	kcm_page_t   * page;
	list_entry_t * iter;
  
    // get KCM lock
	spinlock_lock( &kcm->lock );

    // release all free pages
	LIST_FOREACH( &kcm->free_root , iter )
	{
		page = (kcm_page_t *)LIST_ELEMENT( iter , kcm_page_t , list );
		list_unlink( iter );
		kcm->free_pages_nr --;
		ppm_free_pages( page->page );
	}

    // release all active pages
	LIST_FOREACH( &kcm->active_root , iter )
	{
		page = (kcm_page_t *)LIST_ELEMENT( iter , kcm_page_t , list );
		list_unlink( iter );
		kcm->free_pages_nr --;
		ppm_free_pages( page->page );
	}

    // release all busy pages
	LIST_FOREACH( &kcm->busy_root , iter )
	{
		page = (kcm_page_t *)LIST_ELEMENT( iter , kcm_page_t , list );
		list_unlink( iter );
		kcm->free_pages_nr --;
		ppm_free_pages( page->page );
	}

    // release KCM lock
    spinlock_unlock( &kcm->lock );

    
}

///////////////////////////////
void * kcm_alloc( kcm_t * kcm )
{
	kcm_page_t * page;
	void       * ptr = NULL;   // pointer on block

    // get lock
	spinlock_lock( &kcm->lock );
    
    // get block
    if( list_is_empty( &kcm->active_root ) )  // no active page
    {
		page = (kcm_page_t *)LIST_FIRST( &kcm->active_root , kcm_page_t , list );
        ptr  = get_block( kcm , page );
    }
    else                                      // active page cannot be full
    {
        page = LIST_FIRST( &kcm->active_root , kcm_page_t , list );
        ptr  = get_block( kcm , page );
    }

    // release lock  
	spinlock_unlock(&kcm->lock);

	return ptr;
}

///////////////////////////
void kcm_free( void * ptr )
{
	kcm_page_t * page;
	kcm_t      * kcm;
  
	if( ptr == NULL ) return;
	
	page = (kcm_page_t *)((intptr_t)ptr & CONFIG_PPM_PAGE_MASK);
	kcm  = page->kcm;

    // get lock
	spinlock_lock( &kcm->lock );

    // release block
	put_block( kcm , ptr );

    // release lock  
	spinlock_unlock( &kcm->lock );
}

////////////////////////////
void kcm_print (kcm_t * kcm)
{
	printk("*** KCM type = %d / free_pages = %d / busy_pages = %d / active_pages = %d\n",
           kcm->type , 
           kcm->free_pages_nr , 
           kcm->busy_pages_nr ,
           kcm->active_pages_nr );
}
