/*
 * cluster.c - Cluster-Manager related operations
 * 
 * Author  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_atomic.h>
#include <hal_special.h>
#include <printk.h>
#include <errno.h>
#include <spinlock.h>
#include <core.h>
#include <scheduler.h>
#include <list.h>
#include <cluster.h>
#include <sysfs.h>
#include <boot_info.h>
#include <bits.h>
#include <ppm.h>
#include <thread.h>
#include <kmem.h>
#include <process.h>
#include <dqdt.h>

///////////////////////////////////////////////////////////////////////////////////////////
// Extern global variables
///////////////////////////////////////////////////////////////////////////////////////////

process_t process_zero;



//////////////////////////////////
void cluster_sysfs_register(void)
{
	// TODO
}

/////////////////////////////////////////////////
error_t cluster_init( struct boot_info_s * info )
{
    lpid_t      lpid;     // local process_index
    lid_t       lid;      // local core index

	cluster_t * cluster = LOCAL_CLUSTER;

    // initialize cluster global parameters
	cluster->paddr_width     = info->paddr_width; 
	cluster->x_width         = info->x_width;
	cluster->y_width         = info->y_width;
	cluster->x_size          = info->x_size;
	cluster->y_size          = info->y_size;
	cluster->io_cxy          = info->io_cxy;

    // initialize cluster local parameters
	cluster->cores_nr        = info->cores_nr;
    cluster->cores_in_kernel = info->cores_nr;   // all cpus start in kernel mode

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

    // initialize txt0 lock
	remote_spinlock_init( XPTR( local_cxy , &cluster->txt0_lock ) );

    // initialises DQDT 
    cluster->dqdt_root_level = dqdt_init( info->x_size, 
                                          info->y_size, 
                                          info->y_width );
    cluster->threads_var = 0;
    cluster->pages_var   = 0;

    // initialises embedded PPM
	ppm_init( &cluster->ppm,
              info->pages_nr,
              info->pages_offset );

    // initialises embedded KHM
	khm_init( &cluster->khm );
  
    // initialises embedded KCM 
	kcm_init( &cluster->kcm , KMEM_KCM ); 

    // initialises cores
	for( lid = 0 ; lid < cluster->cores_nr; lid++ )
	{
		core_init( &cluster->core_tbl[lid],    // target core descriptor
	               lid,                        // local core index
	               info->core[lid].gid );      // gid from boot_info_t
	}
	
    // initialises RPC fifo
	rpc_fifo_init( &cluster->rpc_fifo );

    // initialise pref_tbl[] in process manager
	spinlock_init( &cluster->pmgr.pref_lock );
    cluster->pmgr.pref_nr = 0;
    cluster->pmgr.pref_tbl[0] = XPTR( local_cxy , &process_zero );   
    for( lpid = 1 ; lpid < CONFIG_MAX_PROCESS_PER_CLUSTER ; lpid++ )
    {
        cluster->pmgr.pref_tbl[lpid] = XPTR_NULL;
    }

    // initialise local_list in process manager
	spinlock_init( &cluster->pmgr.local_lock );
    list_root_init( &cluster->pmgr.local_root );
    cluster->pmgr.local_nr = 0;

    // initialise copies_lists in process manager
    for( lpid = 1 ; lpid < CONFIG_MAX_PROCESS_PER_CLUSTER ; lpid++ )
    {
	    remote_spinlock_init( XPTR( local_cxy , &cluster->pmgr.copies_lock[lpid] ) );
        cluster->pmgr.copies_nr[lpid] = 0;
        xlist_root_init( XPTR( local_cxy , &cluster->pmgr.copies_root[lpid] ) );
    }    

    hal_wbflush();

    // wait all clusters initialised on barrier located in cluster_io
    remote_barrier( XPTR( cluster->io_cxy , &cluster->barrier ) ,
                    cluster->x_size * cluster->y_size );

	return 0;
} // end cluster_init()

////////////////////////////////////////
bool_t cluster_is_undefined( cxy_t cxy )
{
    cluster_t * cluster = LOCAL_CLUSTER;

    uint32_t y_width = cluster->y_width;

    uint32_t x = cxy >> y_width;
    uint32_t y = cxy & ((1<<y_width)-1);

    if( x >= cluster->x_size ) return true;  
    if( y >= cluster->y_size ) return true;  

    return false;
}

////////////////////////////////////////////////////////////////////////////////////
//  Cores related functions
////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////
void cluster_core_kernel_enter()
{
    cluster_t * cluster = LOCAL_CLUSTER;
	hal_atomic_inc( &cluster->cores_in_kernel );
}

///////////////////////////////
void cluster_core_kernel_exit()
{
    cluster_t * cluster = LOCAL_CLUSTER;
	hal_atomic_dec( &cluster->cores_in_kernel );
}

/////////////////////////////////
lid_t cluster_select_local_core()
{
    uint32_t min = 100;
    lid_t    sel = 0;
    lid_t    lid;

    cluster_t * cluster = LOCAL_CLUSTER;

    for( lid = 0 ; lid < cluster->cores_nr ; lid++ )
    {
        if( cluster->core_tbl[lid].usage < min )
        {
            min = cluster->core_tbl[lid].usage;
            sel = lid;
        }
    } 
    return sel;
}

////////////////////////////////////////////////////////////////////////////////////
//  Process management related functions
////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////
xptr_t cluster_get_reference_process_from_pid( pid_t pid )
{ 
    xptr_t xp;   // extended pointer on process descriptor

    cluster_t * cluster = LOCAL_CLUSTER;

    // get owner cluster and lpid
    cxy_t  owner_cxy = CXY_FROM_PID( pid );
    lpid_t lpid      = LPID_FROM_PID( pid );

    // Check valid PID 
    if( lpid >= CONFIG_MAX_PROCESS_PER_CLUSTER )
    {
        printk("\n[PANIC] in %s : illegal PID\n", __FUNCTION__ );
        hal_core_sleep();
    }

    if( local_cxy == owner_cxy )   // local cluster is owner cluster
    { 
        xp = cluster->pmgr.pref_tbl[lpid];
    }
    else                              // use a remote_lwd to access owner cluster
    {
        xp = (xptr_t)hal_remote_lwd( XPTR( owner_cxy , &cluster->pmgr.pref_tbl[lpid] ) );
    }

    return xp;
}

////////////////////////////////////////////////
error_t cluster_pid_alloc( xptr_t    process_xp,
                           pid_t   * pid )
{
    error_t     error;
    lpid_t      lpid;
    bool_t      found;

    pmgr_t    * pm         = &LOCAL_CLUSTER->pmgr;

    // get the process manager lock
    spinlock_lock( &pm->pref_lock );

    // search an empty slot
    found = false;
    for( lpid = 0 ; lpid < CONFIG_MAX_PROCESS_PER_CLUSTER ; lpid++ )
    {
        if( pm->pref_tbl[lpid] == XPTR_NULL )
        {
            found = true;
            break;
        }
    }

    if( found )
    {
        // register process in pref_tbl[]
        pm->pref_tbl[lpid] = process_xp;
        pm->pref_nr++;

        // returns pid
        *pid = PID( local_cxy , lpid );

        error = 0;
    }
    else
    {
        error = EAGAIN;
    }   

    // release the processs_manager lock
    spinlock_unlock( &pm->pref_lock );

    return error;

} // end cluster_pid_alloc()

/////////////////////////////////////
void cluster_pid_release( pid_t pid )
{
    cxy_t  owner_cxy  = CXY_FROM_PID( pid );
    lpid_t lpid       = LPID_FROM_PID( pid );

    // check pid argument
    if( (lpid >= CONFIG_MAX_PROCESS_PER_CLUSTER) || (owner_cxy != local_cxy) )
    {
        printk("\n[PANIC] in %s : illegal PID\n", __FUNCTION__ );
        hal_core_sleep();
    }

    pmgr_t  * pm = &LOCAL_CLUSTER->pmgr;

    // get the process manager lock
    spinlock_lock( &pm->pref_lock );

    // remove process from pref_tbl[]
    pm->pref_tbl[lpid] = XPTR_NULL;
    pm->pref_nr--;

    // release the processs_manager lock
    spinlock_unlock( &pm->pref_lock );

} // end cluster_pid_release()

///////////////////////////////////////////////////////////
process_t * cluster_get_local_process_from_pid( pid_t pid )
{
    process_t    * ret     = NULL;
    list_entry_t * root    = &LOCAL_CLUSTER->pmgr.local_root;
    list_entry_t * iter;
    process_t    * process;
    
    LIST_FOREACH( root , iter )
    {
        process = LIST_ELEMENT( iter , process_t , local_list );
        if( process->pid == pid )
        {
            ret = process;
            break;
        }
    }
    return ret;

}  // end cluster_get_local_process_from_pid()

//////////////////////////////////////////////////////
void cluster_process_local_link( process_t * process )
{
    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get lock protecting the process manager local list
    spinlock_lock( &pm->local_lock );

    list_add_first( &pm->local_root , &process->local_list );
    pm->local_nr++;

    // release lock protecting the process manager local list
    spinlock_unlock( &pm->local_lock );
}

////////////////////////////////////////////////////////
void cluster_process_local_unlink( process_t * process )
{
    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get lock protecting the process manager local list
    spinlock_lock( &pm->local_lock );

    list_unlink( &process->local_list );
    pm->local_nr--;

    // release lock protecting the process manager local list
    spinlock_unlock( &pm->local_lock );
}

///////////////////////////////////////////////////////
void cluster_process_copies_link( process_t * process )
{
    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get owner cluster identifier CXY and process LPID
    pid_t    pid        = process->pid;
    cxy_t    owner_cxy  = CXY_FROM_PID( pid );
    lpid_t   lpid       = LPID_FROM_PID( pid );

    // get extended pointer on lock protecting copies_list[lpid]
    xptr_t copies_lock  = hal_remote_lwd( XPTR( owner_cxy , &pm->copies_lock[lpid] ) );

    // get extended pointer on the copies_list[lpid] root
    xptr_t copies_root  = hal_remote_lwd( XPTR( owner_cxy , &pm->copies_root[lpid] ) );

    // get extended pointer on the local copies_list entry
    xptr_t copies_entry = XPTR( local_cxy , &process->copies_list );

    // get lock protecting copies_list[lpid] 
    remote_spinlock_lock( copies_lock );

    xlist_add_first( copies_root , copies_entry );
    hal_remote_atomic_add( XPTR( owner_cxy , &pm->copies_nr[lpid] ) , 1 );

    // release lock protecting copies_list[lpid] 
    remote_spinlock_unlock( copies_lock );
}

/////////////////////////////////////////////////////////
void cluster_process_copies_unlink( process_t * process )
{
    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get owner cluster identifier CXY and process LPID
    pid_t    pid        = process->pid;
    cxy_t    owner_cxy  = CXY_FROM_PID( pid );
    lpid_t   lpid       = LPID_FROM_PID( pid );

    // get extended pointer on lock protecting copies_list[lpid]
    xptr_t copies_lock  = hal_remote_lwd( XPTR( owner_cxy , &pm->copies_lock[lpid] ) );

    // get extended pointer on the local copies_list entry
    xptr_t copies_entry = XPTR( local_cxy , &process->copies_list );

    // get lock protecting copies_list[lpid] 
    remote_spinlock_lock( copies_lock );

    xlist_unlink( copies_entry );
    hal_remote_atomic_add( XPTR( owner_cxy , &pm->copies_nr[lpid] ) , -1 );

    // release lock protecting copies_list[lpid] 
    remote_spinlock_unlock( copies_lock );
}

////////////////////////////////////////////////////////////////////////////////////////
// TODO Il me semble que la seule chose que fait ce kernel thread à chaque réveil
// est de mettre à jour la DQDT, et de se rendormir... A-t-on besoin d'un thread ? [AG] 
//////////////////////////////////////////////////////////////////////////////////////////

#if 0
void * cluster_manager_thread( void * arg )
{
	register struct dqdt_cluster_s * root;
	register struct cluster_s      * root_home;

	register uint32_t                tm_start;
	register uint32_t                tm_end;
	register uint32_t                cpu_id;
	struct cluster_s               * cluster;
	struct thread_s                * this;
	struct event_s                   event;
	struct alarm_info_s              info;
	register uint32_t                cntr;
	register bool_t                  isRootMgr;
	register uint32_t                period;

	cpu_enable_all_irq( NULL );

	cluster   = arg;
	this      = CURRENT_THREAD;
	cpu_id    = cpu_get_id();
	root      = dqdt_root;
	root_home = dqdt_root->home;
	isRootMgr = (cluster == root_home) ? true : false;
	cntr      = 0;
	period    = (isRootMgr) ? 
		CONFIG_DQDT_ROOTMGR_PERIOD * MSEC_PER_TICK : 
		CONFIG_DQDT_MGR_PERIOD * MSEC_PER_TICK;

	event_set_senderId(&event, this);
	event_set_priority(&event, E_CHR);
	event_set_handler(&event, &manager_alarm_event_handler);
  
	info.event = &event;
	thread_preempt_disable(CURRENT_THREAD);

    // infinite loop
	while(1)
	{
		tm_start = cpu_time_stamp();
		dqdt_update();
		tm_end   = cpu_time_stamp();

		if(isRootMgr)
		{
			if((cntr % 10) == 0)
			{
				printk(INFO, "INFO: cpu %d, DQDT update ended [ %u - %u ]\n", 
				       cpu_id, 
				       tm_end, 
				       tm_end - tm_start);

				dqdt_print_summary(root);
			}
		}

		alarm_wait( &info , period );
		sched_sleep(this);
		cntr ++;
	}

	return NULL;
} // end cluster_manager_thread()

//////////////////////////////////////////
EVENT_HANDLER(manager_alarm_event_handler)
{
	struct thread_s *manager;
 
	manager = event_get_senderId(event);
 
	thread_preempt_disable(CURRENT_THREAD);

	//printk(INFO, "%s: cpu %d [%u]\n", __FUNCTION__, cpu_get_id(), cpu_time_stamp());

	sched_wakeup(manager);
  
	thread_preempt_enable(CURRENT_THREAD);

	return 0;
}

///////////////////////////////////////////////
EVENT_HANDLER(cluster_key_create_event_handler)
{
	struct cluster_s *cluster;
	struct thread_s *sender;
	ckey_t *ckey;
	uint32_t key;

	sender  = event_get_senderId(event);
	ckey    = event_get_argument(event);
	cluster = current_cluster;
	key     = cluster->next_key;

	while((key < CLUSTER_TOTAL_KEYS_NR) && (cluster->keys_tbl[key] != NULL))
		key ++;

	if(key < CLUSTER_TOTAL_KEYS_NR)
	{
		ckey->val = key;
		cluster->keys_tbl[key] = (void *) 0x1; // Reserved 
		cluster->next_key = key;
		event_set_error(event, 0);
	}
	else
		event_set_error(event, ENOSPC);

	sched_wakeup(sender);
	return 0;
}

///////////////////////////////////////////////
EVENT_HANDLER(cluster_key_delete_event_handler)
{
	struct cluster_s *cluster;
	struct thread_s *sender;
	ckey_t *ckey;
	uint32_t key;

	sender  = event_get_senderId(event);
	ckey    = event_get_argument(event);
	cluster = current_cluster;
	key     = ckey->val;

	if(key < cluster->next_key)
		cluster->next_key = key;

	cluster->keys_tbl[key] = NULL;
	event_set_error(event, 0);

	sched_wakeup(sender);
	return 0;
}

#define _CKEY_CREATE  0x0
#define _CKEY_DELETE  0x1

error_t cluster_do_key_op(ckey_t *key, uint32_t op)
{
	struct event_s event;
	struct thread_s *this;
	struct cluster_s *cluster;
	struct cpu_s *cpu;

	this = CURRENT_THREAD;

	event_set_priority(&event, E_FUNC);
	event_set_senderId(&event, this);
	event_set_argument(&event, key);

	if(op == _CKEY_CREATE)
		event_set_handler(&event, cluster_key_create_event_handler);
	else
		event_set_handler(&event, cluster_key_delete_event_handler);

	cluster = current_cluster;
	cpu     = cluster->bscluster->bscpu;
	event_send(&event, &cpu->re_listner);

	sched_sleep(this);

	return event_get_error(&event);
}

error_t cluster_key_create(ckey_t *key)
{
	return cluster_do_key_op(key, _CKEY_CREATE);
}

error_t cluster_key_delete(ckey_t *key)
{
	return cluster_do_key_op(key, _CKEY_DELETE);
}

void* cluster_getspecific(ckey_t *key)
{
	struct cluster_s *cluster;

	cluster = current_cluster;
	return cluster->keys_tbl[key->val];
}

void  cluster_setspecific(ckey_t *key, void *val)
{
	struct cluster_s *cluster;

	cluster = current_cluster;
	cluster->keys_tbl[key->val] = val;
}
#endif
