/*
 * ppm.c - Per-cluster Physical Pages Manager implementation
 *
 * Authors  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 <printk.h>
#include <list.h>
#include <bits.h>
#include <page.h>
#include <spinlock.h>
#include <thread.h>
#include <cluster.h>
#include <kmem.h>
#include <process.h>
#include <dqdt.h>
#include <ppm.h>



////////////////////////////////////////////////
inline bool_t ppm_page_is_valid( page_t * page )
{
    ppm_t    * ppm  = &LOCAL_CLUSTER->ppm;
	uint32_t   pgnr = (uint32_t)( page - ppm->pages_tbl );
	return (pgnr <= ppm->pages_nr);
}

////////////////////////////////////////////
inline void * ppm_page2base( page_t * page )
{
	ppm_t * ppm = &LOCAL_CLUSTER->ppm;
	return (void*)((page - ppm->pages_tbl) << CONFIG_PPM_PAGE_SHIFT);
}

////////////////////////////////////////////
inline page_t * ppm_base2page( void * base ) 
{
	ppm_t * ppm = &LOCAL_CLUSTER->ppm;
	return (ppm->pages_tbl + (((uint32_t)base ) >> CONFIG_PPM_PAGE_SHIFT));
}

//////////////////////////////////////////
inline ppn_t ppm_page2ppn( page_t * page )
{
	ppm_t  * ppm  = &LOCAL_CLUSTER->ppm;
	return (ppn_t)( page - ppm->pages_tbl );
}

/////////////////////////////////////////
inline page_t * ppm_ppn2page( ppn_t ppn )
{
	ppm_t  * ppm  = &LOCAL_CLUSTER->ppm;
	return &ppm->pages_tbl[ppn];
}

///////////////////////////////////////
inline void * ppm_ppn2base( ppn_t ppn )
{
	return (void*)( ppn << CONFIG_PPM_PAGE_SHIFT );
}

////////////////////////////////////////
inline ppn_t ppm_base2ppn( void * base )
{
	return (ppn_t)( (uint32_t)base >> CONFIG_PPM_PAGE_SHIFT );
}

//////////////////////////////////////////////////
static void ppm_free_pages_nolock( page_t * page )
{
    ppm_t    * ppm         = &LOCAL_CLUSTER->ppm;
    page_t   * pages_tbl   = ppm->pages_tbl;
    uint32_t   page_order  = page->order;
    uint32_t   page_index  = (uint32_t)(page - ppm->pages_tbl);

	page_t   * block;
	uint32_t   block_index;
	uint32_t   current_order;

    // update page descriptor flag
	page_set_flag( page , PG_FREE );

    // update free_lists
	for( current_order = page_order ; current_order < CONFIG_PPM_MAX_ORDER ; current_order++ )
    {
		block_index = page_index ^ (1 << current_order);
		block = pages_tbl + block_index;
    
		if( page_is_flag( block , PG_FREE ) || (block->order != current_order) ) break;

		list_unlink( &block->list );
		ppm->free_pages_nr[current_order] --;
        ppm->total_free_pages -= (1 << current_order);
    
		block->order = 0;
		page_index &= block_index;
	}
  
	block        = pages_tbl + page_index;
	block->order = current_order;

	list_add_first( &ppm->free_pages_root[current_order] , &block->list );
	ppm->free_pages_nr[current_order] ++;
    ppm->total_free_pages += (1 << current_order);

}  // end ppm_free_pages_nolock()

/////////////////////////////////
void ppm_init( ppm_t    * ppm,
               uint32_t   pages_nr,        // total pages number
               uint32_t   pages_offset )   // occupied pages
{
	uint32_t   i;

    // set signature
	ppm->signature = PPM_SIGNATURE;

    // initialize lock protecting the free_pages[] array
	spinlock_init( &ppm->free_lock );

    // initialize free_pages[] array as empty
	ppm->total_free_pages = 0;
	for( i = 0 ; i < CONFIG_PPM_MAX_ORDER ; i++ )
	{
		list_root_init( &ppm->free_pages_root[i] );
		ppm->free_pages_nr[i] = 0;
	}
  
    // initialize dirty_list as empty
    list_root_init( &ppm->dirty_root );

    // initialize pointer on page descriptors array 
	ppm->pages_tbl = (page_t*)( pages_offset << CONFIG_PPM_PAGE_SHIFT );

    // compute size of pages descriptor array rounded to an integer number of pages
    uint32_t bytes = ARROUND_UP( pages_nr * sizeof(page_t), CONFIG_PPM_PAGE_SIZE );

    // compute number of pages required to store page descriptor array
	uint32_t pages_array  = bytes >> CONFIG_PPM_PAGE_SHIFT;

    // compute total number of reserved pages (kernel code & pages_tbl[])
	uint32_t reserved_pages = pages_offset + pages_array;

	// set pages numbers 
	ppm->pages_nr      = pages_nr;
    ppm->pages_offset  = pages_offset;

    // initialises all page descriptors in pages_tbl[]
    // TODO Peut-on accélérer ces monstrueuses boucle ? [AG]
	for( i = 0 ; i < pages_nr ; i++ )
    { 
        page_init( &ppm->pages_tbl[i] );
    } 

    // set PG_RESERVED flag for reserved pages (kernel code & pages_tbl[]) 
	for( i = 0 ; i < reserved_pages ; i++) 
    {
        page_set_flag( &ppm->pages_tbl[i] , PG_RESERVED );
    }

    // initialise the free lists by releasing all non reserved pages
	for( i = 0 ; i < pages_nr ; i++ )
	{
		page_t * page = &ppm->pages_tbl[i];
		if( page_is_flag( page , PG_RESERVED) ) ppm_free_pages_nolock( page );
	}
} // end ppm_init()

////////////////////////////////////////////
page_t * ppm_alloc_pages( uint32_t   order )
{
    uint32_t   current_order;
	page_t   * remaining_block;
	uint32_t   current_size;

    ppm_t    * ppm = &LOCAL_CLUSTER->ppm;

	if( ppm->signature != PPM_SIGNATURE )
    {
        printk("\n[PANIC] in %s : PPM non initialised in cluster %x\n",
               __FUNCTION__ , local_cxy );
        hal_core_sleep();
    }

	if( order >= CONFIG_PPM_MAX_ORDER )
    {
        printk("\n[PANIC] in %s : illegal order argument in cluster %x\n",
               __FUNCTION__ , local_cxy );
        hal_core_sleep();
    }

	page_t * block = NULL;

    // take lock protecting free lists
	spinlock_lock( &ppm->free_lock );

    // find a block larger or equal to requested size
	for( current_order = order ; current_order < CONFIG_PPM_MAX_ORDER ; current_order ++ )
	{
		if( !list_is_empty( &ppm->free_pages_root[current_order] ) )
		{
			block = LIST_FIRST( &ppm->free_pages_root[current_order] , page_t , list );
			list_unlink( &block->list );
			break;
		}
	}

	if( block == NULL ) // return failure
	{
        // release lock protecting free lists
	    spinlock_unlock( &ppm->free_lock );

        return NULL;
    }
  
    // update free-lists after removing a block 
	ppm->total_free_pages -= (1 << current_order);
	ppm->free_pages_nr[current_order] --;  
	current_size = (1 << current_order);

    // split the removed block in smaller sub-blocks if required
    // and update the free-lists accordingly
	while( current_order > order )
	{
		current_order --;
		current_size >>= 1;
    
		remaining_block = block + current_size;
		remaining_block->order = current_order;

		list_add_first( &ppm->free_pages_root[current_order] , &remaining_block->list );
		ppm->free_pages_nr[current_order] ++;
        ppm->total_free_pages += (1 << current_order);
	}
  
    // update page descriptor
    page_clear_flag( block , PG_FREE );
	page_refcount_up( block );
	block->order = order;

    // release lock protecting free lists
	spinlock_unlock( &ppm->free_lock );
  
	return block;
}  // end pmm_alloc-pages()


////////////////////////////////////
void ppm_free_pages( page_t * page )
{
	ppm_t * ppm = &LOCAL_CLUSTER->ppm;
  
    // get lock protecting free_pages[] array
	spinlock_lock( &ppm->free_lock );

	ppm_free_pages_nolock( page ); 

    // release lock protecting free_pages[] array
	spinlock_unlock( &ppm->free_lock );
}

/////////////////////////////
void ppm_print( ppm_t * ppm )
{
	uint32_t       order;
	list_entry_t * iter;
	page_t       * page;

    // get lock protecting free lists
	spinlock_lock( &ppm->free_lock );

	printk("***  PPM state in cluster %x : pages = %d / offset = %d / free = %d ***\n",
	       local_cxy , ppm->pages_nr , ppm->pages_offset , ppm->total_free_pages ); 
	
	for( order = 0 ; order < CONFIG_PPM_MAX_ORDER ; order++ )
	{
		printk("- order = %d / free_pages = %d  [\n",
               order , ppm->free_pages_nr[order] );
		
		LIST_FOREACH( &ppm->free_pages_root[order] , iter )
		{
			page = LIST_ELEMENT( iter , page_t , list );
			printk("%d," , page - ppm->pages_tbl );
		}
    
		printk("]\n", NULL );
	}

    // release lock protecting free lists
	spinlock_unlock( &ppm->free_lock );
}

////////////////////////////////////////
void ppm_assert_order(struct ppm_s *ppm)
{
	uint32_t       order;
	list_entry_t * iter;
	page_t       * page;
	
	for(order=0; order < CONFIG_PPM_MAX_ORDER; order++)
	{
		if( list_is_empty( &ppm->free_pages_root[order] ) ) continue;
		
		LIST_FOREACH( &ppm->free_pages_root[order] , iter )
		{
			page = LIST_ELEMENT( iter , page_t , list );

			if( page->order != order )
            {
	            printk("%s detected inconsistency at order %d, page %d\n", 
	                   __FUNCTION__, order , page - ppm->pages_tbl );
            }
		}
	}
	return;
}

