/*
 * remote_queuelock.c - remote kernel lock with waiting queue implementation.
 *
 * Authors   Alain Greiner     (2016,2017,2018,2019)
 *
 * 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 <thread.h>
#include <scheduler.h>
#include <busylock.h>
#include <remote_queuelock.h>

//////////////////////////////////////////////////////////////////////////////
//                Extern global variables
//////////////////////////////////////////////////////////////////////////////

extern char * lock_type_str[];          // allocated in kernel_init.c


/////////////////////////////////////////////
void remote_queuelock_init( xptr_t   lock_xp,
                            uint32_t type )
{
    // get remote lock cluster and local pointer 
    cxy_t                lock_cxy = GET_CXY( lock_xp );
    remote_queuelock_t * lock_ptr = GET_PTR( lock_xp );

    // initialise taken field
    hal_remote_s32( XPTR( lock_cxy , &lock_ptr->taken ), 0 );

    // initialise xroot field
    xlist_root_init( XPTR( lock_cxy , &lock_ptr->xroot ) );

    // initialise busylock field
    remote_busylock_init( XPTR( lock_cxy , &lock_ptr->lock ) , type );

#if DEBUG_QUEUELOCK_TYPE
thread_t * this = CURRENT_THREAD;
bool_t     cond = (type == DEBUG_QUEUELOCK_TYPE) &&
                  (((lock_cxy == (cxy_t)DEBUG_QUEUELOCK_CXY) &&
                    (lock_ptr == (remote_queuelock_t*)DEBUG_QUEUELOCK_PTR)) ||
                   ((DEBUG_QUEUELOCK_CXY == 0) &&
                    (DEBUG_QUEUELOCK_PTR == 0)));
if( cond )printk("\n[%s] thread[%x,%x] initialise lock %s [%x,%x]\n",
__FUNCTION__, this->process->pid, this->trdid,
lock_type_str[type], lock_cxy, lock_ptr );
#endif

}  // end remote_queuelock_init()

///////////////////////////////////////////////
void remote_queuelock_acquire( xptr_t lock_xp )
{
    thread_t * this = CURRENT_THREAD;

    // get lock cluster and local pointer 
    cxy_t                lock_cxy = GET_CXY( lock_xp );
    remote_queuelock_t * lock_ptr = GET_PTR( lock_xp );

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

#if DEBUG_QUEUELOCK_TYPE
uint32_t   lock_type = hal_remote_l32( XPTR( lock_cxy , &lock_ptr->lock.type ) );
bool_t     cond = (lock_type == DEBUG_QUEUELOCK_TYPE) &&
                  (((lock_cxy == (cxy_t)DEBUG_QUEUELOCK_CXY) &&
                    (lock_ptr == (remote_queuelock_t*)DEBUG_QUEUELOCK_PTR)) ||
                   ((DEBUG_QUEUELOCK_CXY == 0) &&
                    (DEBUG_QUEUELOCK_PTR == 0)));
#endif

    // build extended pointer on busylock protecting queuelock
    xptr_t busylock_xp = XPTR( lock_cxy , &lock_ptr->lock );

    // get busylock 
    remote_busylock_acquire( busylock_xp );

    // block and deschedule if lock already taken
    while( hal_remote_l32( XPTR( lock_cxy, &lock_ptr->taken ) ) )
    {

#if DEBUG_QUEUELOCK_TYPE
if( cond ) printk("\n[%s] thread[%x,%x] BLOCK on q_lock %s [%x,%x]\n",
__FUNCTION__, this->process->pid, this->trdid, 
lock_type_str[lock_type], lock_cxy, lock_ptr );
#endif
        // get pointer on calling thread
        thread_t * this = CURRENT_THREAD;

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

        // register calling thread in waiting list
        xlist_add_last( XPTR( lock_cxy  , &lock_ptr->xroot ),
                        XPTR( local_cxy , &this->wait_xlist ) );

        // release busylock
        remote_busylock_release( busylock_xp );

        // deschedule calling thread
        sched_yield("wait remote_queuelock");

        // get busylock 
        remote_busylock_acquire( busylock_xp );
    }

#if DEBUG_QUEUELOCK_TYPE
if( cond ) printk("\n[%s] thread[%x,%x] ACQUIRE q_lock %s [%x,%x]\n",
__FUNCTION__, this->process->pid, this->trdid, 
lock_type_str[lock_type], lock_cxy, lock_ptr );
#endif

    // update remote_queuelock state
    hal_remote_s32( XPTR( lock_cxy , &lock_ptr->taken ) , 1 );

    // release busylock
    remote_busylock_release( busylock_xp );

    hal_fence();

}  // end remote_queuelock_acquire()

////////////////////////////////////////////////
void remote_queuelock_release( xptr_t  lock_xp )
{
    // memory barrier before lock release
    hal_fence();

    // get lock cluster and local pointer 
    cxy_t                lock_cxy = GET_CXY( lock_xp );
    remote_queuelock_t * lock_ptr = GET_PTR( lock_xp );

#if DEBUG_QUEUELOCK_TYPE
thread_t * this      = CURRENT_THREAD;
uint32_t   lock_type = hal_remote_l32( XPTR( lock_cxy , &lock_ptr->lock.type ) );
bool_t     cond = (lock_type == DEBUG_QUEUELOCK_TYPE) &&
                  (((lock_cxy == (cxy_t)DEBUG_QUEUELOCK_CXY) &&
                    (lock_ptr == (remote_queuelock_t*)DEBUG_QUEUELOCK_PTR)) ||
                   ((DEBUG_QUEUELOCK_CXY == 0) &&
                    (DEBUG_QUEUELOCK_PTR == 0)));
#endif

    // build extended pointer on busylock protecting queuelock
    xptr_t busylock_xp = XPTR( lock_cxy , &lock_ptr->lock );

    // get busylock
    remote_busylock_acquire( busylock_xp );

#if DEBUG_QUEUELOCK_TYPE
if( cond ) printk("\n[%s] thread[%x,%x] RELEASE q_lock %s [%x,%x]\n",
__FUNCTION__, this->process->pid, this->trdid,
lock_type_str[lock_type], lock_cxy, lock_ptr );
#endif

    // update remote_queuelock state
    hal_remote_s32( XPTR( lock_cxy , &lock_ptr->taken ) , 0 );

    // unblock first waiting thread if waiting list not empty
    if( xlist_is_empty( XPTR( lock_cxy, &lock_ptr->xroot ) ) == false )
    {
        // get extended pointer on first waiting thread
        xptr_t root_xp   = XPTR( lock_cxy , &lock_ptr->xroot );
        xptr_t     thread_xp  = XLIST_FIRST( root_xp , thread_t , wait_xlist );
        cxy_t      thread_cxy = GET_CXY( thread_xp );
        thread_t * thread_ptr = GET_PTR( thread_xp );

#if DEBUG_QUEUELOCK_TYPE
if( cond )
{
    trdid_t     trdid   = hal_remote_l32( XPTR( thread_cxy , &thread_ptr->trdid ) );
    process_t * process = hal_remote_lpt( XPTR( thread_cxy , &thread_ptr->process ) );
    pid_t       pid     = hal_remote_l32( XPTR( thread_cxy , &process->pid ) );
    printk("\n[%s] thread[%x,%x] UNBLOCK thread[%x,%x] / q_lock %s [%x,%x]\n",
    __FUNCTION__, this->process->pid, this->trdid, trdid, pid, 
    lock_type_str[lock_type], lock_cxy, lock_ptr );
}
#endif

        // remove this thread from waiting queue
        xlist_unlink( XPTR( thread_cxy , &thread_ptr->wait_xlist ) );

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

    // release busylock
    remote_busylock_release( busylock_xp );

}  // end remote_queuelock_release()



