/*
 * cluster.c - Cluster-Manager related operations
 *
 * Author  Ghassan Almaless (2008,2009,2010,2011,2012)
 *         Mohamed Lamine Karaoui (2015)
 *         Alain Greiner (2016,2017,2018)
 *
 * 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_atomic.h>
#include <hal_special.h>
#include <hal_ppm.h>
#include <remote_fifo.h>
#include <printk.h>
#include <errno.h>
#include <spinlock.h>
#include <core.h>
#include <chdev.h>
#include <scheduler.h>
#include <list.h>
#include <cluster.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
/////////////////////////////////////////////////////////////////////////////////////

extern process_t           process_zero;     // allocated in kernel_init.c file
extern chdev_directory_t   chdev_dir;        // allocated in kernel_init.c file

///////////////////////////////////////////////n
error_t cluster_init( struct boot_info_s * info )
{
    error_t         error;
    lpid_t          lpid;     // local process_index
    lid_t           lid;      // local core index
    uint32_t        i;        // index in loop on external peripherals
    boot_device_t * dev;      // pointer on external peripheral
    uint32_t        func;     // external peripheral functionnal type

	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->x_max           = info->x_max; // [FIXME]
	cluster->y_max           = info->y_max; // [FIXME]
	cluster->io_cxy          = info->io_cxy;

    // initialize external peripherals channels
    for( i = 0 ; i < info->ext_dev_nr ; i++ )
    {
        dev  = &info->ext_dev[i];
        func = FUNC_FROM_TYPE( dev->type );   
        if( func == DEV_FUNC_TXT ) cluster->nb_txt_channels = dev->channels;
        if( func == DEV_FUNC_NIC ) cluster->nb_nic_channels = dev->channels;
        if( func == DEV_FUNC_IOC ) cluster->nb_ioc_channels = dev->channels;
        if( func == DEV_FUNC_FBF ) cluster->nb_fbf_channels = dev->channels;
    }

    // initialize cluster local parameters
	cluster->cores_nr        = info->cores_nr;

    // initialize the lock protecting the embedded kcm allocator
	spinlock_init( &cluster->kcm_lock );

#if DEBUG_CLUSTER_INIT
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : thread %x enters for cluster %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , cycle );
#endif

    // initialises DQDT
    cluster->dqdt_root_level = dqdt_init( info->x_max, // [FIXME]
                                          info->y_max, // [FIXME]
                                          info->y_width ) - 1;

    // initialises embedded PPM
	error = hal_ppm_init( info );

    if( error )
    {
        printk("\n[ERROR] in %s : cannot initialize PPM in cluster %x\n",
               __FUNCTION__ , local_cxy );
        return ENOMEM;
    }

#if( DEBUG_CLUSTER_INIT & 1 )
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : PPM initialized in cluster %x / cycle %d\n",
__FUNCTION__ , local_cxy , cycle );
#endif

    // initialises embedded KHM
	khm_init( &cluster->khm );

#if( DEBUG_CLUSTER_INIT & 1 )
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : KHM initialized in cluster %x at cycle %d\n",
__FUNCTION__ , local_cxy , hal_get_cycles() );
#endif

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

#if( DEBUG_CLUSTER_INIT & 1 )
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : KCM initialized in cluster %x at cycle %d\n",
__FUNCTION__ , local_cxy , hal_get_cycles() );
#endif

    // initialises all cores descriptors 
	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
	}

#if( DEBUG_CLUSTER_INIT & 1 )
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : cores initialized in cluster %x / cycle %d\n",
__FUNCTION__ , local_cxy , cycle );
#endif

    // initialises RPC FIFOs
	for( lid = 0 ; lid < cluster->cores_nr; lid++ )
    {
	    local_fifo_init( &cluster->rpc_fifo[lid] );
        cluster->rpc_threads[lid] = 0;
    }

#if( DEBUG_CLUSTER_INIT & 1 )
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s : RPC fifo inialized in cluster %x at cycle %d\n",
__FUNCTION__ , local_cxy , hal_get_cycles() );
#endif

    // 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
	remote_spinlock_init( XPTR( local_cxy , &cluster->pmgr.local_lock ) );
    xlist_root_init( XPTR( local_cxy , &cluster->pmgr.local_root ) );
    cluster->pmgr.local_nr = 0;

    // initialise copies_lists in process manager
    for( lpid = 0 ; 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] ) );
    }

#if DEBUG_CLUSTER_INIT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_INIT < cycle )
printk("\n[DBG] %s , thread %x exit for cluster %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , cycle );
#endif

    hal_fence();

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

/////////////////////////////////
lid_t cluster_select_local_core( void )
{
    uint32_t      min = 1000;
    lid_t         sel = 0;
    uint32_t      nthreads;
    lid_t         lid;
    scheduler_t * sched;

    cluster_t * cluster = LOCAL_CLUSTER;

    for( lid = 0 ; lid < cluster->cores_nr ; lid++ )
    {
        sched    = &cluster->core_tbl[lid].scheduler;
        nthreads = sched->u_threads_nr + sched->k_threads_nr;

        if( nthreads < min )
        {
            min = nthreads;
            sel = lid;
        }
    }
    return sel;
}

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


//////////////////////////////////////////////////////
xptr_t cluster_get_process_from_pid_in_cxy( cxy_t cxy,
                                            pid_t pid )
{
    xptr_t      root_xp;       // xptr on root of list of processes in owner cluster
    xptr_t      lock_xp;       // xptr on lock protecting this list
    xptr_t      iter_xp;       // iterator
    xptr_t      current_xp;    // xptr on current process descriptor
    bool_t      found;

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

    // get lock & root of list of copies from owner cluster
    root_xp = XPTR( owner_cxy , &cluster->pmgr.copies_root[lpid] );
    lock_xp = XPTR( owner_cxy , &cluster->pmgr.copies_lock[lpid] );

    // take the lock protecting the list of processes
    remote_spinlock_lock( lock_xp );

    // scan list of processes
    found = false;
    XLIST_FOREACH( root_xp , iter_xp )
    {
        current_xp  = XLIST_ELEMENT( iter_xp , process_t , copies_list );

        if( GET_CXY( current_xp ) == cxy )
        {
            found = true;
            break;
        }
    }

    // release the lock protecting the list of processes
    remote_spinlock_unlock( lock_xp );

    // return extended pointer on process descriptor in owner cluster
    if( found ) return current_xp;
    else        return XPTR_NULL;

}  // end cluster_get_process_from_pid_in_cxy()


//////////////////////////////////////////////////////
xptr_t cluster_get_owner_process_from_pid( pid_t pid )
{
    xptr_t      root_xp;       // xptr on root of list of processes in owner cluster
    xptr_t      lock_xp;       // xptr on lock protecting this list
    xptr_t      iter_xp;       // iterator
    xptr_t      current_xp;    // xptr on current process descriptor
    process_t * current_ptr;   // local pointer on current process
    pid_t       current_pid;   // current process identifier
    bool_t      found;

    cluster_t * cluster = LOCAL_CLUSTER;

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

    // get lock & root of list of process in owner cluster
    root_xp = XPTR( owner_cxy , &cluster->pmgr.local_root );
    lock_xp = XPTR( owner_cxy , &cluster->pmgr.local_lock );

    // take the lock protecting the list of processes
    remote_spinlock_lock( lock_xp );

    // scan list of processes in owner cluster
    found = false;
    XLIST_FOREACH( root_xp , iter_xp )
    {
        current_xp  = XLIST_ELEMENT( iter_xp , process_t , local_list );
        current_ptr = GET_PTR( current_xp );
        current_pid = hal_remote_lw( XPTR( owner_cxy , &current_ptr->pid ) );

        if( current_pid == pid )
        {
            found = true;
            break;
        }
    }

    // release the lock protecting the list of processes
    remote_spinlock_unlock( lock_xp );

    // return extended pointer on process descriptor in owner cluster
    if( found ) return current_xp;
    else        return XPTR_NULL;

}  // end cluster_get_owner_process_from_pid()


//////////////////////////////////////////////////////////
xptr_t cluster_get_reference_process_from_pid( pid_t pid )
{
    xptr_t ref_xp;   // extended pointer on reference 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 )  return XPTR_NULL;

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

    return ref_xp;
}

///////////////////////////////////////////////
error_t cluster_pid_alloc( process_t * process,
                           pid_t     * pid )
{
    lpid_t      lpid;
    bool_t      found;

#if DEBUG_CLUSTER_PID_ALLOC
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PID_ALLOC < cycle )
printk("\n[DBG] %s : thread %x enters in cluster %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , cycle );
#endif

    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] = XPTR( local_cxy , process );
        pm->pref_nr++;

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

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

        return 0;
    }
    else
    {
        // release the processs_manager lock
        spinlock_unlock( &pm->pref_lock );

        return -1;
    }

#if DEBUG_CLUSTER_PID_ALLOC
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PID_ALLOC < cycle )
printk("\n[DBG] %s : thread %x exit in cluster %x / pid %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , *pid , cycle );
#endif

} // end cluster_pid_alloc()

/////////////////////////////////////
void cluster_pid_release( pid_t pid )
{

#if DEBUG_CLUSTER_PID_RELEASE
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PID_RELEASE < cycle )
printk("\n[DBG] %s : thread %x enters in cluster %x / pid %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , pid , cycle );
#endif

    cxy_t  owner_cxy  = CXY_FROM_PID( pid );
    lpid_t lpid       = LPID_FROM_PID( pid );

    pmgr_t  * pm = &LOCAL_CLUSTER->pmgr;

    // check lpid 
    assert( (lpid < CONFIG_MAX_PROCESS_PER_CLUSTER),
    "illegal LPID = %d" , lpid );

    // check owner cluster
    assert( (owner_cxy == local_cxy) ,
    "local_cluster %x !=  owner_cluster %x" , local_cxy , owner_cxy );

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

#if DEBUG_CLUSTER_PID_RELEASE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PID_RELEASE < cycle )
printk("\n[DBG] %s : thread %x exit in cluster %x / cycle %d\n",
__FUNCTION__ , CURRENT_THREAD , local_cxy , cycle );
#endif

} // end cluster_pid_release()

///////////////////////////////////////////////////////////
process_t * cluster_get_local_process_from_pid( pid_t pid )
{
    xptr_t         process_xp;
    process_t    * process_ptr;
    xptr_t         root_xp;
    xptr_t         iter_xp;
    bool_t         found;

    found   = false;
    root_xp = XPTR( local_cxy , &LOCAL_CLUSTER->pmgr.local_root );

    XLIST_FOREACH( root_xp , iter_xp )
    {
        process_xp  = XLIST_ELEMENT( iter_xp , process_t , local_list );
        process_ptr = (process_t *)GET_PTR( process_xp );
        if( process_ptr->pid == pid )
        {
            found = true;
            break;
        }
    }

    if (found ) return process_ptr;
    else        return NULL;

}  // end cluster_get_local_process_from_pid()

//////////////////////////////////////////////////////
void cluster_process_local_link( process_t * process )
{
    reg_t    save_sr;

    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get extended pointers on local process list root & lock
    xptr_t root_xp = XPTR( local_cxy , &pm->local_root );
    xptr_t lock_xp = XPTR( local_cxy , &pm->local_lock );

    // get lock protecting the process manager local list
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // register process in local list
    xlist_add_last( root_xp , XPTR( local_cxy , &process->local_list ) );
    pm->local_nr++;

    // release lock protecting the process manager local list
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}

////////////////////////////////////////////////////////
void cluster_process_local_unlink( process_t * process )
{
    reg_t save_sr;

    pmgr_t * pm = &LOCAL_CLUSTER->pmgr;

    // get extended pointers on local process list lock
    xptr_t lock_xp = XPTR( local_cxy , &pm->local_lock );

    // get lock protecting the process manager local list
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // remove process from local list
    xlist_unlink( XPTR( local_cxy , &process->local_list ) );
    pm->local_nr--;

    // release lock protecting the process manager local list
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}

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

#if DEBUG_CLUSTER_PROCESS_COPIES
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PROCESS_COPIES < cycle )
printk("\n[DBG] %s enters / cluster %x / process %x / cycle %d\n",
__FUNCTION__ , local_cxy , process , cycle );
#endif

    // 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  = XPTR( owner_cxy , &pm->copies_lock[lpid] );

    // get extended pointer on the copies_list[lpid] root
    xptr_t copies_root  = 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_busy( copies_lock , &irq_state );

    // add copy to copies_list
    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_busy( copies_lock , irq_state );

#if DEBUG_CLUSTER_PROCESS_COPIES
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PROCESS_COPIES < cycle )
printk("\n[DBG] %s exit / cluster %x / process %x / cycle %d\n",
__FUNCTION__ , local_cxy , process , cycle );
#endif

}  // end cluster_process_copies_link()

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

#if DEBUG_CLUSTER_PROCESS_COPIES
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PROCESS_COPIES < cycle )
printk("\n[DBG] %s enters / cluster %x / process %x / cycle %d\n",
__FUNCTION__ , local_cxy , process , cycle );
#endif

    // 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  = 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_busy( copies_lock , &irq_state );

    // remove copy from copies_list
    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_busy( copies_lock , irq_state );

#if DEBUG_CLUSTER_PROCESS_COPIES
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_CLUSTER_PROCESS_COPIES < cycle )
printk("\n[DBG] %s exit / cluster %x / process %x / cycle %d\n",
__FUNCTION__ , local_cxy , process , cycle );
#endif

}  // end cluster_process_copies_unlink()

///////////////////////////////////////////
void cluster_processes_display( cxy_t cxy )
{
    xptr_t        root_xp;
    xptr_t        lock_xp;
    xptr_t        iter_xp;
    xptr_t        process_xp;
    cxy_t         txt0_cxy;
    chdev_t     * txt0_ptr;
    xptr_t        txt0_xp;
    xptr_t        txt0_lock_xp;
    reg_t         txt0_save_sr;     // save SR to take TXT0 lock in busy mode      

    assert( (cluster_is_undefined( cxy ) == false),
    "illegal cluster index" );

    // get extended pointer on root and lock for local process list in cluster
    root_xp = XPTR( cxy , &LOCAL_CLUSTER->pmgr.local_root );
    lock_xp = XPTR( cxy , &LOCAL_CLUSTER->pmgr.local_lock );

    // get pointers on TXT0 chdev
    txt0_xp  = chdev_dir.txt_tx[0];
    txt0_cxy = GET_CXY( txt0_xp );
    txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on TXT0 lock
    txt0_lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get lock on local process list 
    remote_spinlock_lock( lock_xp );

    // get TXT0 lock in busy waiting mode
    remote_spinlock_lock_busy( txt0_lock_xp , &txt0_save_sr );
     
    // display header
    nolock_printk("\n***** processes in cluster %x / cycle %d\n",
    cxy , (uint32_t)hal_get_cycles() );

    // loop on all processes in cluster cxy 
    XLIST_FOREACH( root_xp , iter_xp )
    {
        process_xp = XLIST_ELEMENT( iter_xp , process_t , local_list );
        process_display( process_xp );
    }

    // release TXT0 lock in busy waiting mode
    remote_spinlock_unlock_busy( txt0_lock_xp , txt0_save_sr );

    // release lock on local process list
    remote_spinlock_unlock( lock_xp );

}  // end cluster_processes_display() 


