/*
 * remote_condvar.c - distributed kernel condvar implementaion 
 * 
 * Author   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 <hal_types.h>
#include <hal_remote.h>
#include <hal_irqmask.h>
#include <thread.h>
#include <kmem.h>
#include <printk.h>
#include <process.h>
#include <vmm.h>
#include <xlist.h>
#include <remote_mutex.h>
#include <remote_condvar.h>

///////////////////////////////////////////////////
xptr_t remote_condvar_from_ident( intptr_t  ident )
{
    // get pointer on local process_descriptor
    process_t * process = CURRENT_THREAD->process;

    // get extended pointer on reference process
    xptr_t      ref_xp = process->ref_xp;

    // get cluster and local pointer on reference process 
    cxy_t          ref_cxy = GET_CXY( ref_xp );
    process_t    * ref_ptr = (process_t *)GET_PTR( ref_xp );

    // get extended pointer on root of condvars list 
    xptr_t root_xp = XPTR( ref_cxy , &ref_ptr->condvar_root );
   
    // scan reference process condvars list
    xptr_t             iter_xp;
    xptr_t             condvar_xp;
    cxy_t              condvar_cxy;
    struct remote_condvar_s * condvar_ptr;
    intptr_t           current;
    bool_t             found = false;
            
    XLIST_FOREACH( root_xp , iter_xp )
    {
        condvar_xp  = XLIST_ELEMENT( iter_xp , remote_condvar_t , list );
        condvar_cxy = GET_CXY( condvar_xp );
        condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );
        current     = (intptr_t)hal_remote_lpt( XPTR( condvar_cxy , &condvar_ptr->ident ) );   
        if( ident == current )
        {
            found = true;
            break;
        }
    }

    if( found == false )  return XPTR_NULL;
    else                  return condvar_xp;

}  // end remote_condvar_from_ident()

///////////////////////////////////////////////
error_t remote_condvar_create( intptr_t ident )
{
    xptr_t             condvar_xp;
    remote_condvar_t * condvar_ptr;

    // get pointer on local process descriptor
    process_t * process = CURRENT_THREAD->process;

    // get extended pointer on reference process
    xptr_t      ref_xp = process->ref_xp;

    // get reference process cluster and local pointer
    cxy_t       ref_cxy = GET_CXY( ref_xp );
    process_t * ref_ptr = (process_t *)GET_PTR( ref_xp );

    // allocate memory for condvar descriptor
    if( ref_cxy == local_cxy )                  // local cluster is the reference 
    {
        kmem_req_t req;   
        req.type      = KMEM_CONDVAR;
        req.flags     = AF_ZERO;
        condvar_ptr   = kmem_alloc( &req );
        condvar_xp    = XPTR( local_cxy , condvar_ptr );
    }
    else                                       // reference is remote
    {
        rpc_kcm_alloc_client( ref_cxy , KMEM_CONDVAR , &condvar_xp );
        condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );
    }

    if( condvar_ptr == NULL ) return ENOMEM;

    // initialise condvar 
    hal_remote_spt( XPTR( ref_cxy , &condvar_ptr->ident      ) , (void*)ident );
    xlist_entry_init( XPTR( ref_cxy , &condvar_ptr->list ) );
    xlist_root_init( XPTR( ref_cxy , &condvar_ptr->root ) );

    // register  condvar in reference process xlist
    xptr_t root_xp = XPTR( ref_cxy , &ref_ptr->condvar_root );
    xptr_t xp_list = XPTR( ref_cxy , &condvar_ptr->list );

    remote_spinlock_lock( XPTR( ref_cxy , &ref_ptr->sync_lock ) );
    xlist_add_first( root_xp , xp_list );
    remote_spinlock_unlock( XPTR( ref_cxy , &ref_ptr->sync_lock ) );

    return 0;

}  // end remote_condvar_create()

////////////////////////////////////////////////
void remote_condvar_destroy( xptr_t condvar_xp )
{
    // get pointer on local process descriptor
    process_t * process = CURRENT_THREAD->process;

    // get extended pointer on reference process
    xptr_t      ref_xp = process->ref_xp;

    // get reference process cluster and local pointer
    cxy_t       ref_cxy = GET_CXY( ref_xp );
    process_t * ref_ptr = (process_t *)GET_PTR( ref_xp );

    // get condvar cluster and local pointer
    cxy_t              condvar_cxy = GET_CXY( condvar_xp );
    remote_condvar_t * condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );

    // remove condvar from reference process xlist
    remote_spinlock_lock( XPTR( ref_cxy , &ref_ptr->sync_lock ) );
    xlist_unlink( XPTR( condvar_cxy , &condvar_ptr->list ) );
    remote_spinlock_unlock( XPTR( ref_cxy , &ref_ptr->sync_lock ) );

    // release memory allocated for condvaraphore descriptor
    if( condvar_cxy == local_cxy )                            // reference is local
    {
        kmem_req_t  req;
        req.type = KMEM_BARRIER;
        req.ptr  = condvar_ptr;
        kmem_free( &req );
    }
    else                                                  // reference is remote
    {
        rpc_kcm_free_client( condvar_cxy , condvar_ptr , KMEM_BARRIER );
    }

}  // end remote_condvar_destroy()

////////////////////////////////////////////
void remote_condvar_wait( xptr_t condvar_xp,
                          xptr_t mutex_xp )
{
    // unlock the mutex
    remote_mutex_unlock( mutex_xp );

    thread_t * this = CURRENT_THREAD; 

    // get condvar cluster an local pointer
    remote_condvar_t * condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );
    cxy_t              condvar_cxy = GET_CXY( condvar_xp );

    // get extended pointer on condvar waiting queue 
    xptr_t root_xp  = XPTR( condvar_cxy , &condvar_ptr->root );

    // get extended pointer on calling thread xlist_entry
    xptr_t entry_xp = XPTR( local_cxy , &this->wait_list ); 

    // register the calling thread in the condvar waiting queue
    remote_spinlock_lock( XPTR( condvar_cxy , &condvar_ptr->lock ) ); 
    xlist_add_last( root_xp , entry_xp );
    remote_spinlock_unlock( XPTR( condvar_cxy , &condvar_ptr->lock ) ); 

    // block the calling thread 
    thread_block( CURRENT_THREAD , THREAD_BLOCKED_USERSYNC );
    sched_yield( NULL );

    // lock the mutex before return
    remote_mutex_unlock( mutex_xp );

}  // end remote_condvar_wait()

///////////////////////////////////////////////
void remote_condvar_signal( xptr_t condvar_xp )
{
    reg_t     irq_state;

    // get condvar cluster an local pointer
    remote_condvar_t * condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );
    cxy_t              condvar_cxy = GET_CXY( condvar_xp );

    // get extended pointer on condvar waiting queue 
    xptr_t root_xp  = XPTR( condvar_cxy , &condvar_ptr->root );

    if( xlist_is_empty( root_xp ) ) return;

    // disable interrupts
	hal_disable_irq( &irq_state );
  
    // get extended pointer on the first waiting thread
    xptr_t thread_xp = XLIST_FIRST_ELEMENT( root_xp , thread_t , wait_list );

    // remove the first waiting thread from queue
    remote_spinlock_lock( XPTR( condvar_cxy , &condvar_ptr->lock ) );
    xlist_unlink( XPTR( condvar_cxy , &condvar_ptr->list ) );
    remote_spinlock_unlock( XPTR( condvar_cxy , &condvar_ptr->lock ) );

    // unblock first waiting thread
    thread_unblock( thread_xp , THREAD_BLOCKED_USERSYNC ); 

    // restore interrupts
	hal_restore_irq( irq_state );

}  // end remote_condvar_signal()

//////////////////////////////////////////////////
void remote_condvar_broadcast( xptr_t condvar_xp )
{
    reg_t    irq_state;

    // get condvar cluster an local pointer
    remote_condvar_t * condvar_ptr = (remote_condvar_t *)GET_PTR( condvar_xp );
    cxy_t              condvar_cxy = GET_CXY( condvar_xp );

    // get extended pointer on condvar waiting queue 
    xptr_t root_xp  = XPTR( condvar_cxy , &condvar_ptr->root );

    if( xlist_is_empty( root_xp ) ) return;

    // disable interrupts
    hal_disable_irq( &irq_state );
  
    // loop on waiting threads
    xptr_t  iter_xp;
    xptr_t  thread_xp;
    XLIST_FOREACH( root_xp , iter_xp )
    {
        // get extended pointer on waiting thread
        thread_xp = XLIST_ELEMENT( iter_xp , thread_t , wait_list );
        
        // remove waiting thread from queue
        remote_spinlock_lock( XPTR( condvar_cxy , &condvar_ptr->lock ) );
        xlist_unlink( XPTR( condvar_cxy , &condvar_ptr->list ) );
        remote_spinlock_unlock( XPTR( condvar_cxy , &condvar_ptr->lock ) );

        // unblock waiting thread
        thread_unblock( thread_xp , THREAD_BLOCKED_USERSYNC ); 
    }

    // restore interrupts
    hal_restore_irq( irq_state );

}  // end remote_condvar_broadcast()
