/*
 * alarm.c - timer based kernel alarm implementation            
 *
 * Author     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 <printk.h>
#include <list.h>
#include <thread.h>
#include <core.h>
#include <alarm.h>

////////////////////////////////////////////////////////////////////////////////////////////
// This static function registers the alarm identified by the <alarm> & <cxy> arguments 
// in the list of alarms rooted in the core identified by the <core> argument.
// When the existing list of alarms is not empty, it scan the list to insert the new
// alarm in the right place to respect the absolute dates ordering. 
////////////////////////////////////////////////////////////////////////////////////////////
// @ cxy    : cluster containing both the new alarm and the core.
// @ alarm  : local pointer on the alarm.
// @ core   : local pointer on the core.
////////////////////////////////////////////////////////////////////////////////////////////
static void alarm_register( cxy_t     cxy,
                            alarm_t * alarm,
                            core_t  * core )
{
    // get alarm date
    cycle_t new_date = hal_remote_l64( XPTR( cxy , &alarm->date ) );

    // build local pointer on root of alarms list 
    list_entry_t * root = &core->alarms_root;

    // build local pointer on new alarm list_entry
    list_entry_t * new  = &alarm->list;

    // insert new alarm to respect dates order
    if( list_remote_is_empty( cxy , &core->alarms_root ) )  // list empty
    {
        list_remote_add_first( cxy , root , new ); 
    }
    else                                                   // list non empty
    {
        list_entry_t * iter;        // local pointer on current list_entry in existing list
        alarm_t      * iter_alarm;  // local pointer on current alarm in existing list
        cycle_t        iter_date;   // date of current alarm in existing list
        bool_t         done = false;

        for( iter = hal_remote_lpt( XPTR( cxy , &root->next ) ) ;
             (iter != root) && (done == false) ;
             iter = hal_remote_lpt( XPTR( cxy , &iter->next ) ) )
        {
            // get local pointer on pred and next for iter
            list_entry_t * prev = hal_remote_lpt( XPTR( cxy , &iter->pred ) );

            // get local pointer on current alarm 
            iter_alarm = LIST_ELEMENT( iter , alarm_t , list );

            // get date for current alarm 
            iter_date = hal_remote_l64( XPTR( cxy , &iter_alarm->date ) );

            // insert new alarm just before current when required
            if( iter_date > new_date )  
            {
                hal_remote_spt( XPTR( cxy , &new->next ) , iter );
                hal_remote_spt( XPTR( cxy , &new->pred ) , prev );

                hal_remote_spt( XPTR( cxy , &iter->pred ) , new );
                hal_remote_spt( XPTR( cxy , &prev->next ) , new );
                
                done = true;
            }
        }  // end for
        
        if( done == false ) // new_date is larger than all registered dates
        {
            list_remote_add_last( cxy, root , new );
        }
    }
}  // end alarm_register()
            

///////////////////////////////////
void alarm_init( alarm_t *  alarm )
{
    alarm->linked   = false;
    list_entry_init( &alarm->list );
}

///////////////////////////////////////
void alarm_start( xptr_t     thread_xp,
                  cycle_t    date,
                  void     * func_ptr,
                  xptr_t     args_xp )
{
    // get cluster and local pointer on target thread
    thread_t * tgt_ptr = GET_PTR( thread_xp );
    cxy_t      tgt_cxy = GET_CXY( thread_xp );

// check alarm state
assert( __FUNCTION__ , (hal_remote_l32( XPTR(tgt_cxy,&tgt_ptr->alarm.linked)) == false ),
"alarm already started");

    // get local pointer on core running target thread
    core_t * core = hal_remote_lpt( XPTR( tgt_cxy , &tgt_ptr->core ) );

    // build extended pointer on lock protecting alarms list
    xptr_t lock_xp = XPTR( tgt_cxy , &core->alarms_lock );
  
    // initialize alarm descriptor
    hal_remote_s64( XPTR( tgt_cxy , &tgt_ptr->alarm.date     ) , date );
    hal_remote_spt( XPTR( tgt_cxy , &tgt_ptr->alarm.func_ptr ) , func_ptr );
    hal_remote_s64( XPTR( tgt_cxy , &tgt_ptr->alarm.args_xp  ) , args_xp );
    hal_remote_s32( XPTR( tgt_cxy , &tgt_ptr->alarm.linked   ) , true );

    // take the lock
    remote_busylock_acquire( lock_xp );

    // register alarm in core list
    alarm_register( tgt_cxy , &tgt_ptr->alarm , core );

    //release the lock
    remote_busylock_release( lock_xp );

}  // end alarm_start()


/////////////////////////////////////
void alarm_stop( xptr_t   thread_xp )
{
    // get cluster and local pointer on target thread
    thread_t * tgt_ptr = GET_PTR( thread_xp );
    cxy_t      tgt_cxy = GET_CXY( thread_xp );

    // get local pointer on core running target thread
    core_t * core = hal_remote_lpt( XPTR( tgt_cxy , &tgt_ptr->core ) );

    // build extended pointer on lock protecting alarms list
    xptr_t lock_xp = XPTR( tgt_cxy , &core->alarms_lock );
  
    // take the lock
    remote_busylock_acquire( lock_xp );

    // unlink the alarm from the list rooted in core
    list_remote_unlink( tgt_cxy , &tgt_ptr->alarm.list );

    // update alarm state
    hal_remote_s32( XPTR( tgt_cxy , &tgt_ptr->alarm.linked ) , false );

    //release the lock
    remote_busylock_release( lock_xp );

}  // end alarm_stop()


//////////////////////////////////////
void alarm_update( xptr_t   thread_xp,
                   cycle_t  new_date )
{
    // get cluster and local pointer on target thread
    thread_t * tgt_ptr = GET_PTR( thread_xp );
    cxy_t      tgt_cxy = GET_CXY( thread_xp );

    // get local pointer on core running target thread
    core_t * core = hal_remote_lpt( XPTR( tgt_cxy , &tgt_ptr->core ) );

    // build extended pointer on lock protecting alarms list
    xptr_t lock_xp = XPTR( tgt_cxy , &core->alarms_lock );
  
    // take the lock
    remote_busylock_acquire( lock_xp );

    // unlink the alarm from the core list
    list_remote_unlink( tgt_cxy , &tgt_ptr->alarm.list );

    // update the alarm date and state
    hal_remote_s64( XPTR( tgt_cxy , &tgt_ptr->alarm.date   ) , new_date );
    hal_remote_s32( XPTR( tgt_cxy , &tgt_ptr->alarm.linked ) , true );

    // register alarm in core list
    alarm_register( tgt_cxy , &tgt_ptr->alarm , core );
    
    // release the lock
    remote_busylock_release( lock_xp );

}  // end alarm_update()


