/*
 * signal.c - signal-management related operations implementation
 * 
 * Author  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 <hal_types.h>
#include <errno.h>
#include <thread.h>
#include <process.h>
#include <core.h>
#include <signal.h>

SIGNAL_HANDLER(kill_sigaction)
{
	struct thread_s *this;
  
	this = CURRENT_THREAD;
	this->state = S_KERNEL;

	printk(INFO, "INFO: Recieved signal %d, pid %d, tid %x, core %d  [ KILLED ]\n",
	       sig,
	       this->process->pid,
	       this,
	       cpu_get_id());

	sys_thread_exit((void*)EINTR);
}

//////////////////////////////////////////////////
void signal_manager_init( process_t * process )
{
	memset(&process->sig_mgr, 0, sizeof(process->sig_mgr));
	process->sig_mgr.sigactions[SIGCHLD] = SIG_IGNORE;
	process->sig_mgr.sigactions[SIGURG]  = SIG_IGNORE;
}

//////////////////////////////////////
void signal_rise( process_t * process, 
                  uint32_t    sig )
{
	thread_t     * thread;
	uint32_t       i;

	spinlock_lock( &process->th_lock );

	for( i = 0 ; i < process->th_nr ; i++ )
	{
		thread = process->th_tbl[i];
		hal_atomic_or( &thread->signals , (1 << sig) );
	}

	spinlock_unlock( &process->th_lock );

	sig_dmsg("\n[INFO] %s : %d threads have been signaled for process %d\n",
                        __FUNCTION__, process->th_nr , process->pid );

}  // end signal_rise()

///////////////////////////////////////////
RPC_DECLARE( __signal_rise,                             \
                RPC_RET( RPC_RET_PTR(error_t, err)),    \
                RPC_ARG( RPC_ARG_VAL(pid_t,   pid),     \
                         RPC_ARG_VAL(uint32_t,  sig))     \
           )
{
         process_t   * process;
        struct hnode_s  *hnode;

        /* Avoid killing process0 and init */
        /* FIXME: Zero should not be hard-coded but obtains with something like MAIN_KERN */
        if( ((pid == PID_MIN_GLOBAL) || (pid == PID_MIN_GLOBAL+1))
                        && (current_cid == 0) )
        {
                *err = EPERM;
                sig_dmsg(1, "%s: can't kill process %u on cluster %u\n",           \
                                __FUNCTION__, PID_GET_LOCAL(pid), current_cid);
                goto SYS_RISE_ERR_PID;
        }

        /* Step 1 : lock the process manager */
        processs_manager_lock();

        /* Step 2 : Get the process' address */
        /* Case 1 : current cluster is the anchor and the owner. */
        if ( PID_GET_CLUSTER(pid) == current_cid )
        {
                sig_dmsg(1, "%s: process %u is in the processs manager array of cluster  %u\n",       \
                                __FUNCTION__, pid, current_cid);
                process = process_lookup(pid)->process;

        }
        else /* Case 2 : current cluster is not the anchor, so the struct
              * process_s is in its hash table.
              */
        {
                sig_dmsg(1, "%s: process %u is in the processs manager hash table of cluster  %u\n",  \
                                __FUNCTION__, pid, current_cid);
                hnode = hfind(processs_manager_get_htable(), (void*)pid);
                process    = ( process_t*) container_of(hnode,          \
                                 process_t, t_hnode);
        }

        /* Step 4 : check process' address */
        if ( process == NULL )
        {
                *err = ESRCH;
                goto SYS_RISE_ERR;
        }

        /* Step 5 : deliver signal */
        if((sig == SIGTERM) || (sig == SIGKILL))
                *err = signal_rise_all(process, sig);
        else
                *err = signal_rise_one(process, sig);

        /* Step 6 : unlock processs manager */
        processs_manager_unlock();

        return;

SYS_RISE_ERR:
        processs_manager_unlock();
SYS_RISE_ERR_PID:
        sig_dmsg(1, "%s: Cluster %u has not deliver signal %u to process %u (err %u)\n",  \
                        __FUNCTION__, current_cid, sig, err );

        return;
}

///////////////////////////////
error_t sys_kill( pid_t    pid, 
                  uint32_t sig)
{
    cxy_t       owner_cxy;    // process owner cluster
    lpid_t      owner_lpid;   // local process identifier 
    xptr_t      root_xp;      // extended pointer on root of xlist of process copies
    xptr_t      lock_xp;      // extended pointer on remote_spinlock protecting this list
    xptr_t      iter_xp;      // iterator for process copies list
    xptr_t      process_xp;   // local pointer on process copy 
    cxy_t       process_cxy;  // cluster of process copy
    process_t * process_ptr;  // local pointer on process copy 
	error_t     error;

    // get local pointer on local cluster manager
    cluster_t * cluster = LOCAL_CLUSTER;

    // get owner process cluster and lpid
    owner_cxy  = CXY_FROM_PID( pid );
    owner_lpid = LPID_FROM_PID( pid );

    // get extended pointers on copies root and lock
    root_xp = XPTR( owner_cxy , &cluster->copies_root[lpid] );
    lock_xp = XPTR( owner_cxy , &cluster->copies_lock[lpid] );

    // take the lock protecting the copies
    remote_spinlock_lock( lock_xp );

    // TODO the loop below sequencialize the RPCs
    // they could be pipelined...
 
    // traverse the list of copies
    XLIST_FOREACH( root_xp , iter_xp )
    {
        process_xp  = XLIST_ELEMENT( iter_xp , process_t , copies_list );
        process_cxy = GET_CXY( process_xp );
        process_ptr = (process_t *)GET_PTR( process_xp );

        if( process_xy == local_cxy )   // process copy is local
        {
            error = signal_rise( process_ptr , sig );
        }
        else                           // process copy is remote
        {
            rpc_signal_rise_client( process_cxy , process_ptr , sig );
        }
    }

	return 0;
}

////////////////////////////////////
void signal_notify( thread_s * this)
{
	register uint32_t sig_state;
	register uint32_t sig;
	register struct sig_mgr_s *sig_mgr;
	uint32_t irq_state;

	sig_state = this->info.sig_state & this->info.sig_mask;
	sig       = 0;
 
	while((sig_state != 0) && ((sig_state & 0x1) == 0) && (sig < SIG_NR))
	{
		sig ++; 
		sig_state >>= 1;
	}
  
	if(sig)
	{
		cpu_disable_all_irq(&irq_state);

		if(thread_isSignaled(this))
		{
			cpu_restore_irq(irq_state);
			return;
		}

		thread_set_signaled(this);
		cpu_restore_irq(irq_state);

		spinlock_lock(&this->lock);
		this->info.sig_state &= ~(1 << sig);
		spinlock_unlock(&this->lock);

		sig_mgr = &this->process->sig_mgr;

		if(sig_mgr->sigactions[sig] == SIG_IGNORE)
			return;

		if(sig_mgr->sigactions[sig] == SIG_DEFAULT)
			kill_sigaction(sig);

		cpu_signal_notify(this, sig_mgr->sigactions[sig], sig);
	}
}
