/*
 * 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 ny the <new_alarm> argument 
// 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. 
////////////////////////////////////////////////////////////////////////////////////////////
// @ new_alarm  : local pointer on the new alarm.
// @ core       : local pointer on the target core.
////////////////////////////////////////////////////////////////////////////////////////////
static void alarm_register( alarm_t * new_alarm,
                            core_t  * core )
{
    list_entry_t * current;          // pointer on current list_entry in existing list
    list_entry_t * previous;         // pointer on previous list_entry in existing list
    alarm_t      * current_alarm;    // pointer on current alarm in existing list
    cycle_t        current_date;     // date of current alarm in existing list

    bool_t         done = false;

    // get pointers on root of alarms and lock
    list_entry_t * root = &core->alarms_root;
    busylock_t   * lock = &core->alarms_lock;

    // get pointer on new_alarm list_entry 
    list_entry_t * new_entry = &new_alarm->list;

    // get new_alarm date
    cycle_t        new_date = new_alarm->date;

    // take the lock
    busylock_acquire( lock );

    // insert new alarm to respect dates order
    if( list_is_empty( root ) )                     // list empty
    {
        list_add_first( root , new_entry ); 
    }
    else                                            // list non empty
    {
        for( current = root->next ;
             (current != root) && (done == false) ;
             current = current->next )
        {
            // get pointer on previous entry in existing list
            previous = current->pred;

            // get pointer on current alarm 
            current_alarm = LIST_ELEMENT( current , alarm_t , list );

            // get date for current alarm 
            current_date  = current_alarm->date;

            if( current_date > new_date )  // insert new alarm just before current
            {
                new_entry->next = current;
                new_entry->pred = previous;

                current->pred  = new_entry;
                previous->next = new_entry;
                
                done = true;
            }
        }  // end for
        
        if( done == false ) // new_date is larger than all registered dates
        {
            list_add_last( root , new_entry );
        }
    }

    // release the lock
    busylock_release( lock );

}  // end alarm_register()

//////////////////////////////////////
void alarm_start( cycle_t    date,
                  void     * func_ptr,
                  xptr_t     args_xp,
                  thread_t * thread )
{
    // get pointer on alarm
    alarm_t * alarm = &thread->alarm;

    // initialize alarm descriptor
    alarm->date     = date;
    alarm->func_ptr = func_ptr;
    alarm->args_xp = args_xp;
    
    // register alarm in core list
    alarm_register( alarm , thread->core );

}  // end alarm_start()

/////////////////////////////////////
void alarm_update( thread_t * thread,
                   cycle_t    new_date )
{
    // get pointer on alarm
    alarm_t * alarm = &thread->alarm;

    // get pointer on core 
    core_t   * core = thread->core;

    // get pointer on lock protecting the alarms list
    busylock_t   * lock = &core->alarms_lock;

    // unlink the alarm from the core list
    busylock_acquire( lock );
    list_unlink( &alarm->list );
    busylock_release( lock );

    // update the alarm date
    alarm->date = new_date;

    // register alarm in core list
    alarm_register( alarm , core );
    
}  // end alarm_update()

////////////////////////////////////
void alarm_stop( thread_t * thread )
{
    // get pointer on alarm
    alarm_t * alarm = &thread->alarm;

    // get pointer on core 
    core_t * core = thread->core;

    // get pointer on lock protecting the alarms list
    busylock_t * lock = &core->alarms_lock;

    // unlink the alarm from the list rooted in core
    busylock_acquire( lock );
    list_unlink( &alarm->list );
    busylock_release( lock );

}  // end alarm_stop()

