/*
 * sys_fork.c - Kernel function implementing the "fork" system call.
 * 
 * Authors  Alain Greiner  (2016,2017)
 *
 * 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_context.h>
#include <hal_switch.h>
#include <hal_atomic.h>
#include <errno.h>
#include <printk.h>
#include <core.h>
#include <cluster.h>
#include <list.h>
#include <thread.h>
#include <scheduler.h>
#include <kmem.h>
#include <dqdt.h>
#include <process.h>

#include <syscalls.h>

////////////////////
int sys_fork( void )
{
	process_t       * parent_process_ptr;   // pointer on local parent process descriptor
    xptr_t            parent_thread_xp;     // extended pointer on parent thread descriptor
    pid_t             parent_pid;           // parent process identifier
    thread_t        * parent_thread_ptr;    // local pointer on local parent thread descriptor
    cxy_t             parent_cxy;           // parent thread cluster

    pid_t             child_pid;            // child process identifier
    thread_t        * child_thread_ptr;     // local pointer on remote child thread descriptor
    cxy_t             child_cxy;            // target cluster for forked child process 
 
    xptr_t            ref_process_xp;       // extended pointer on reference parent process
    cxy_t             ref_process_cxy;      // cluster of reference parent process
    process_t       * ref_process_ptr;      // local pointer on reference parent process

	error_t           error;
    

    // get pointers on local parent process and thread
	parent_thread_ptr  = CURRENT_THREAD;
    parent_thread_xp   = XPTR( local_cxy , parent_thread_ptr );
	parent_process_ptr = parent_thread_ptr->process;
    parent_pid         = parent_process_ptr->pid;
    parent_cxy         = local_cxy;

#if (DEBUG_SYS_FORK || CONFIG_INSTRUMENTATION_SYSCALLS)
uint64_t     tm_start = hal_get_cycles();
#endif

#if DEBUG_SYS_FORK
if( DEBUG_SYS_FORK < tm_start )
printk("\n[DBG] %s : thread[%x,%x] enter / cycle =  %d\n",
__FUNCTION__, parent_pid, parent_thread_ptr->trdid, (uint32_t)tm_start );
#endif

    // get infos on reference parent process
    ref_process_xp  = parent_process_ptr->ref_xp;
    ref_process_cxy = GET_CXY( ref_process_xp );
    ref_process_ptr = GET_PTR( ref_process_xp );

    // check parent process children number from reference
    xptr_t   children_xp = XPTR( ref_process_cxy , &ref_process_ptr->children_nr );
    if( hal_remote_atomic_add( children_xp , 1 ) >= CONFIG_PROCESS_MAX_CHILDREN )
	{

#if DEBUG_SYSCALLS_ERROR
printk("\n[ERROR] in %s : thread[%x,%x] cannot fork : too much children\n",
__FUNCTION__, parent_pid, parent_thread_ptr->trdid );
#endif
	    hal_remote_atomic_add ( children_xp , -1 );
        parent_thread_ptr->errno = EAGAIN;
        return -1;
	}

    // Select target cluster for child process and main thread.
    // If placement is not user-defined, it is defined by the DQDT. 
	if( parent_thread_ptr->fork_user )
	{
        child_cxy = parent_thread_ptr->fork_cxy;
        parent_thread_ptr->fork_user = false;
	}
	else                                  // DQDT placement
	{
		child_cxy = dqdt_get_cluster_for_process();
	}

#if (DEBUG_SYS_FORK & 1 )
if( DEBUG_SYS_FORK < tm_start )
printk("\n[%s] thread[%x,%x] selected cluster %x\n",
__FUNCTION__, parent_pid, parent_thread_ptr->trdid, child_cxy );
#endif

    // call process_make_fork in target cluster
    if( child_cxy == local_cxy )
    {
        error = process_make_fork( ref_process_xp,
                                   parent_thread_xp,
                                   &child_pid,
                                   &child_thread_ptr );
    }
    else
    {
        rpc_process_make_fork_client( child_cxy,
                                      ref_process_xp,
                                      parent_thread_xp,
                                      &child_pid,
                                      &child_thread_ptr,
                                      &error );
    }

    if( error )
    {

#if DEBUG_SYSCALLS_ERROR
printk("\n[ERROR] in %s : thread[%x,%x] cannot fork\n",
__FUNCTION__, parent_pid, parent_thread_ptr->trdid );
#endif
        parent_thread_ptr->errno = EAGAIN;
        return -1;
    }

    // set remote child FPU_context from parent_thread register values
    // only when the parent thread is the FPU owner
	if( CURRENT_THREAD->core->fpu_owner == parent_thread_ptr )
	{
		hal_fpu_context_save( XPTR( child_cxy , child_thread_ptr ) );
	}

    // set remote child CPU context from parent_thread register values
    hal_cpu_context_fork( XPTR( child_cxy , child_thread_ptr ) );

    // From this point, both parent and child threads execute the following code,
    // but they can be distinguished by the (CURRENT_THREAD,local_cxy) values.
    // - parent unblock child, and return child PID to user application.
    // - child thread does nothing, and return 0 to user pplication 
    // The child thread will only execute it when it is unblocked by parent thread.

    thread_t * current = CURRENT_THREAD;

#if (DEBUG_SYS_FORK || CONFIG_INSTRUMENTATION_SYSCALLS)
uint64_t     tm_end = hal_get_cycles();
#endif

#if DEBUG_SYS_FORK
if( DEBUG_SYS_FORK < tm_end )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__, current->process->pid, current->trdid, (uint32_t)tm_end );
#endif

    if( (current == parent_thread_ptr) && (local_cxy == parent_cxy) )   // parent thread
    {
        // parent_thread unblock child_thread
        thread_unblock( XPTR( child_cxy , child_thread_ptr ) , THREAD_BLOCKED_GLOBAL );

        // only parent contribute to instrumentation

#if CONFIG_INSTRUMENTATION_SYSCALLS
hal_atomic_add( &syscalls_cumul_cost[SYS_FORK] , tm_end - tm_start );
hal_atomic_add( &syscalls_occurences[SYS_FORK] , 1 );
#endif
        return child_pid;
    }
	else                                                               // child_thread
    {
        return 0;
    }

}  // end sys_fork()

