/*
 * 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 <kernel_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 <kmem.h>
#include <kcm.h>

//////////////////////////////////////////////////////////////////////////////////////
// This static function returns pointer on an allocated block from an active page.
// It returns NULL if no block available in selected page.
// It changes the page status if required.
//////////////////////////////////////////////////////////////////////////////////////
// @ kcm   : pointer on kcm allocator.
// @ ptr   : pointer on active kcm page to use.
/////////////////////////////////////////////////////////////////////////////////////
static void * kcm_get_block( kcm_t      * kcm,
                             kcm_page_t * page )
{
	assert( page->active , __FUNCTION__ , "kcm page should be active" );

	// get first block available
	int32_t index = bitmap_ffs( page->bitmap , kcm->blocks_nr );

	assert( (index != -1) , __FUNCTION__ , "kcm page should not be full" );

	// allocate block
	bitmap_clear( page->bitmap , index );

	// increase page refcount
	page->refcount ++;

	// change the page to busy no more free 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->base + index * kcm->block_size );

}  // kcm_get_block()

/////////////////////////////////////////////////////////////////////////////////////
// This static function releases a previously allocated block.
// It changes the page status if required.
/////////////////////////////////////////////////////////////////////////////////////
// @ kcm   : pointer on kcm allocator.
// @ ptr   : pointer on block to be released.
/////////////////////////////////////////////////////////////////////////////////////
static void kcm_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->base) / 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 ++;
	}
}  // kcm_put_block()

/////////////////////////////////////////////////////////////////////////////////////
// This static function allocates 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;
	kmem_req_t   req;

	// get one page from local PPM
	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 ENOMEM;
	}

	// get 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->base          = (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;

}  // freelist_populate()

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

	// get a new page from PPM if freelist empty
	if( kcm->free_pages_nr == 0 )
	{
		error = freelist_populate( kcm );
		if( error ) 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 --;

	return page;

} // freelist_get()


//////////////////////////////
void kcm_init( kcm_t    * kcm,
	           uint32_t   type )
{
	uint32_t     blocks_nr;
	uint32_t     block_size;
	uint32_t     remaining;

	// initialize lock
	spinlock_init( &kcm->lock );

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

	// initialize 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
	block_size      = ARROUND_UP( kmem_type_size( 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;

	kcm_dmsg("\n[INFO] %s : KCM %s initialised / block_size = %d / blocks_nr = %d\n",
	         __FUNCTION__ , kmem_type_str( type ) , block_size , blocks_nr );

}  // kcm_init()

///////////////////////////////
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 );

}  // kcm_destroy()

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

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

	// get an active page
	if( list_is_empty( &kcm->active_root ) )  // no active page => get one
	{
		kcm_dmsg("\n[INFO] %s : enters for type %s but no active page => get one\n",
		         __FUNCTION__ , kmem_type_str( kcm->type ) );

		// get a page from free list
		page = freelist_get( kcm );
		if( page == NULL ) return NULL;

		// insert page in active list
		list_add_first( &kcm->active_root , &page->list );
		kcm->active_pages_nr ++;
		page->active = 1;
	}
	else                     // get first page from active list
	{
		kcm_dmsg("\n[INFO] %s : enters for type %s with an active page\n",
		         __FUNCTION__ , kmem_type_str( kcm->type ) );

		// get page pointer from active list
		page = (kcm_page_t *)LIST_FIRST( &kcm->active_root , kcm_page_t , list );
	}

	// get a block from selected active page
	// cannot fail, as an active page cannot be full...
	ptr  = kcm_get_block( kcm , page );

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

	kcm_dmsg("\n[INFO] %s : allocated one block of type %s / ptr = %x\n",
	         __FUNCTION__ , kmem_type_str( kcm->type ) , (uint32_t)ptr );

	return ptr;

}  // kcm_alloc()

///////////////////////////
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
	kcm_put_block( kcm , ptr );

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

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