/*
 * remote_condvar.c - remote kernel condition variable implementation.
 *
 * Authors     Alain Greiner (2016,2017,2018,2019,2020)
 *
 * 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 <thread.h>
#include <scheduler.h>
#include <xlist.h>
#include <remote_mutex.h>
#include <remote_busylock.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 = GET_PTR( ref_xp );

    // get extended pointer on condvars list 
    xptr_t root_xp = XPTR( ref_cxy , &ref_ptr->condvar_root );
    xptr_t lock_xp = XPTR( ref_cxy , &ref_ptr->sync_lock );

    // get lock protecting synchro lists
    remote_queuelock_acquire( lock_xp );
 
    // scan reference process condvar list
    xptr_t             iter_xp;
    xptr_t             condvar_xp;
    cxy_t              condvar_cxy;
    remote_condvar_t * 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 = GET_PTR( condvar_xp );
        current = (intptr_t)hal_remote_lpt( XPTR( condvar_cxy , &condvar_ptr->ident ) );   

        if( current == ident )
        {
            found = true;
            break;
        }
    }

    // relese lock protecting synchros lists
    remote_queuelock_release( lock_xp );
 
    if( found == false )  return XPTR_NULL;
    else                  return condvar_xp;

}  // end remote_condvar_from_ident()

/////////////////////////////////////////////////
error_t remote_condvar_create( intptr_t   ident )
{
    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
    condvar_ptr = kmem_alloc( bits_log2(sizeof(remote_condvar_t)) , AF_ZERO );

    if( condvar_ptr == NULL )
    {
        printk("\n[ERROR] in %s : cannot create condvar\n", __FUNCTION__ );
        return -1;
    }

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

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

    remote_queuelock_acquire( XPTR( ref_cxy , &ref_ptr->sync_lock ) );
    xlist_add_first( root_xp , list_xp );
    remote_queuelock_release( 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 = GET_PTR( ref_xp );

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

    // get remote pointer on waiting queue root
    xptr_t root_xp = XPTR( condvar_cxy , &condvar_ptr->root );
  
    if( !xlist_is_empty( root_xp ) )   // user error
    {
        printk("WARNING in %s for thread %x in process %x : "
               "destroy condvar, but  waiting threads queue not empty\n", 
               __FUNCTION__ , CURRENT_THREAD->trdid , CURRENT_THREAD->process->pid );
    }

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

    // release memory allocated for condvar descriptor
    kmem_remote_free( ref_cxy , condvar_ptr , bits_log2(sizeof(remote_condvar_t)) );

}  // end remote_convar_destroy()

////////////////////////////////////////////
void remote_condvar_wait( xptr_t condvar_xp,
                          xptr_t mutex_xp )
{
    thread_t * this = CURRENT_THREAD;

    // check calling thread can yield
    thread_assert_can_yield( this , __FUNCTION__ );

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

    // register the calling thread in condvar waiting queue
    xlist_add_last( XPTR( condvar_cxy , &condvar_ptr->root ),
                    XPTR( local_cxy   , &this->wait_xlist ) );

    // block the calling thread
    thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_USERSYNC );

    // release the mutex
    remote_mutex_unlock( mutex_xp );

    // deschedule
    sched_yield("blocked on condvar");

    // re-acquire the mutex
    remote_mutex_lock( mutex_xp );

}  // end remote_condvar_wait()

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

    // does nothing if waiting queue empty
    if( xlist_is_empty( XPTR( condvar_cxy, &condvar_ptr->root ) ) == false )
    {
         // get first waiting thread
         xptr_t thread_xp = XLIST_FIRST( XPTR( condvar_cxy , &condvar_ptr->root ),
                                         thread_t , wait_xlist );

         // remove this waiting thread from queue
         thread_t * thread_ptr = GET_PTR( thread_xp );
         cxy_t      thread_cxy = GET_CXY( thread_xp );
         xlist_unlink( XPTR( thread_cxy , &thread_ptr->wait_xlist ) );

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

}  // end remote_condvar_signal()

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

    // does nothing if waiting queue empty
    while( xlist_is_empty( XPTR( condvar_cxy , &condvar_ptr->root ) ) == false )
    {
         // get first waiting thread
         xptr_t thread_xp = XLIST_FIRST( XPTR( condvar_cxy , &condvar_ptr->root ),
                                         thread_t , wait_xlist );

         // remove this waiting thread from queue
         thread_t * thread_ptr = GET_PTR( thread_xp );
         cxy_t      thread_cxy = GET_CXY( thread_xp );
         xlist_unlink( XPTR( thread_cxy , &thread_ptr->wait_xlist ) );

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

