//==========================================================================
//
//      sched/lottery.cxx
//
//      Lottery scheduler class implementation
//
//==========================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998,1999 Cygnus Solutions.  All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   nickg
// Contributors:        nickg
// Date:        1997-09-16
// Purpose:     Lottery scheduler class implementation
// Description: This file contains the implementations of
//              Cyg_Scheduler_Implementation and
//              Cyg_SchedThread_Implementation.
//              
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/kernel.h>

#include <cyg/kernel/ktypes.h>         // base kernel types
#include <cyg/infra/cyg_trac.h>        // tracing macros
#include <cyg/infra/cyg_ass.h>         // assertion macros

#include <cyg/kernel/sched.hxx>        // our header
#include <cyg/kernel/intr.hxx>         // interrupt defines, for Cyg_HAL_Clock

#include <cyg/hal/hal_arch.h>          // Architecture specific definitions


#include <cyg/kernel/thread.inl>       // thread inlines
#include <cyg/kernel/sched.inl>        // scheduler inlines

#ifdef CYGSEM_KERNEL_SCHED_LOTTERY

#define CYG_ENABLE_TRACE 1

//==========================================================================
// Cyg_Scheduler_Implementation class static members

#ifdef CYGSEM_KERNEL_SCHED_TIMESLICE

cyg_count32 Cyg_Scheduler_Implementation::timeslice_count =
                                        CYGNUM_KERNEL_SCHED_TIMESLICE_TICKS;

#endif

//==========================================================================
// Cyg_Scheduler_Implementation class members

// -------------------------------------------------------------------------
// Constructor.

Cyg_Scheduler_Implementation::Cyg_Scheduler_Implementation()
{
    CYG_REPORT_FUNCTION();

    total_tickets = 0;
    rand_seed = 1;
}

// -------------------------------------------------------------------------
// Choose the best thread to run next

Cyg_Thread *Cyg_Scheduler_Implementation::schedule()
{
    CYG_REPORT_FUNCTION();

#ifdef CYG_HAL_POWERPC

    // PowerPc specific version of random number generator.
    register cyg_int32 r1 asm("r4");
    r1 = rand_seed;
    asm(
        "li     7,0;"
        "ori    7,7,33614;"
        "mulhwu 5,7,%0;"
        "mullw  6,7,%0;"
        "srawi  6,6,1;"
        "add    %0,5,6;"
        "cmpwi  %0,0;"
        "bge    1f;"
        "slwi   %0,%0,1;"
        "srwi   %0,%0,1;"
        "addi   %0,%0,1;"
        "1:;"
        : "=r"(r1)
        : "0"(r1)
        : "r5", "r6", "r7"
        );
    rand_seed = r1;    

#else
#if 1
    rand_seed = (rand_seed * 1103515245) + 1234;
    cyg_int32 r1 = rand_seed & 0x7FFFFFFF;
#else    
    // Generic implementation of RNG.
#if( CYG_BYTEORDER == CYG_MSBFIRST )
#define _LO 1    
#define _HI 0
#else
#define _LO 0    
#define _HI 1
#endif    
    union { cyg_int64 r64; cyg_int32 r32[2]; } u;
    u.r64 = (cyg_int64)rand_seed * 33614LL;
    cyg_int32 r1 = u.r32[_HI] + (u.r32[_LO]>>1);
    if( r1 < 0 )
        r1 = (r1 & 0x7FFFFFFF) + 1;
    rand_seed = r1;
#undef _LO
#undef _HI
#endif    
#endif    

    cyg_int32 ticket = r1 % total_tickets;
    cyg_int32 tick = ticket;
    Cyg_Thread *thread = run_queue.highpri();

    // Search the run queue for the thread with the
    // given ticket.
    while( ticket > 0 )
    {
        ticket -= thread->priority;
        if( ticket <= 0 ) break;
        thread = thread->next;
        
        CYG_ASSERT( thread != run_queue.highpri(), "Looping in scheduler");
    }

    CYG_TRACE3( CYG_ENABLE_TRACE,
        "seed %08x ticket %d thread %08x",
        rand_seed, tick, thread);

    // If the thread has any compensation tickets, take them away since
    // it has just won.

    if( thread->compensation_tickets > 0 )
    {
        thread->priority -= thread->compensation_tickets;
        total_tickets -= thread->compensation_tickets;
        thread->compensation_tickets = 0;
    }
 
    // Re-insert thread at head of list. This reduces runtime by
    // putting the large ticket holders at the front of the list.
    
//    run_queue.remove(thread);
//    run_queue.enqueue(thread);

    CYG_CHECK_DATA_PTR( thread, "Invalid next thread pointer");            
    CYG_ASSERTCLASS( thread, "Bad next thread" );
    
    return thread;
}

// -------------------------------------------------------------------------

void Cyg_Scheduler_Implementation::add_thread(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();

    // If the thread is on some other queue, remove it
    // here.
    if( thread->queue != NULL )
    {
        thread->queue->remove(thread);
        thread->queue = NULL;
    }
    
    total_tickets += thread->priority;

    run_queue.enqueue(thread);
}

// -------------------------------------------------------------------------

void Cyg_Scheduler_Implementation::rem_thread(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();

    run_queue.remove(thread);

    total_tickets -= thread->priority;

    // Compensate the thread for the segment of the quantum that
    // it used. This makes it more likely to win the lottery next time
    // it is scheduled. We only do this for threads that have voluntarily
    // given up the CPU.

//    if( thread->get_state() != Cyg_Thread::RUNNING )
    {
#if 0        
        cyg_uint32 hal_ticks;
        HAL_CLOCK_READ( &hal_ticks );
        thread->compensation_tickets = thread->priority *
            CYGNUM_KERNEL_COUNTERS_RTC_PERIOD / hal_ticks;
#else
        thread->compensation_tickets = (thread->priority *
            CYGNUM_KERNEL_SCHED_TIMESLICE_TICKS) / timeslice_count;
        
#endif        
        thread->priority += thread->compensation_tickets;
    }
}

// -------------------------------------------------------------------------
// register thread with scheduler

void Cyg_Scheduler_Implementation::register_thread(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();
        
    // No registration necessary in this scheduler
}

// -------------------------------------------------------------------------

// deregister thread
void Cyg_Scheduler_Implementation::deregister_thread(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();
        
    // No registration necessary in this scheduler    
}
    
// -------------------------------------------------------------------------
// Test the given priority for uniqueness

cyg_bool Cyg_Scheduler_Implementation::unique( cyg_priority priority)
{
    CYG_REPORT_FUNCTION();
        
    // Priorities are not unique
    return true;
}

//==========================================================================
// Support for timeslicing option

#ifdef CYGSEM_KERNEL_SCHED_TIMESLICE

void Cyg_Scheduler_Implementation::timeslice()
{
    CYG_REPORT_FUNCTION();

    if( --timeslice_count <= 0 )
    {
        CYG_INSTRUMENT_SCHED(TIMESLICE,0,0);
        
        // Force a reschedule on each timeslice
        need_reschedule = true;
        timeslice_count = CYGNUM_KERNEL_SCHED_TIMESLICE_TICKS;
    }
}

#endif

//==========================================================================
// Cyg_Cyg_SchedThread_Implementation class members

Cyg_SchedThread_Implementation::Cyg_SchedThread_Implementation
(
    CYG_ADDRWORD sched_info
)
{
    CYG_REPORT_FUNCTION();

    priority = cyg_priority(sched_info);
    
    // point the next and prev field at this thread.
    
    next = prev = CYG_CLASSFROMBASE(Cyg_Thread,
                                    Cyg_SchedThread_Implementation,
                                    this);
}

// -------------------------------------------------------------------------
// Insert thread in front of this

void Cyg_SchedThread_Implementation::insert( Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();
        
    thread->next        = CYG_CLASSFROMBASE(Cyg_Thread,
                                            Cyg_SchedThread_Implementation,
                                            this);
    thread->prev        = prev;
    prev->next          = thread;
    prev                = thread;    
}

// -------------------------------------------------------------------------
// remove this from queue

void Cyg_SchedThread_Implementation::remove()
{
    CYG_REPORT_FUNCTION();
        
    next->prev          = prev;
    prev->next          = next;
    next = prev         = CYG_CLASSFROMBASE(Cyg_Thread,
                                            Cyg_SchedThread_Implementation,
                                            this);
}

// -------------------------------------------------------------------------
// Yield the processor to another thread

void Cyg_SchedThread_Implementation::yield()
{
    CYG_REPORT_FUNCTION();
        

}

//==========================================================================
// Cyg_ThreadQueue_Implementation class members

void Cyg_ThreadQueue_Implementation::enqueue(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();

    // Always put thread at head of queue
    if( queue == NULL ) queue = thread;
    else
    {
        queue->insert(thread);
//        queue->next->insert(thread);
//        queue = thread;
    }
    
    thread->queue = CYG_CLASSFROMBASE(Cyg_ThreadQueue,
                                      Cyg_ThreadQueue_Implementation,
                                      this);    
}

// -------------------------------------------------------------------------

Cyg_Thread *Cyg_ThreadQueue_Implementation::dequeue()
{
    CYG_REPORT_FUNCTION();
        
    if( queue == NULL ) return NULL;
    
    Cyg_Thread *thread = queue;
    
    if( thread->next == thread )
    {
        // sole thread on list, NULL out ptr
        queue = NULL;
    }
    else
    {
        // advance to next and remove thread
        queue = thread->next;
        thread->remove();
    }

    thread->queue = NULL;

    return thread;
}

// -------------------------------------------------------------------------

Cyg_Thread *Cyg_ThreadQueue_Implementation::highpri()
{
    CYG_REPORT_FUNCTION();
        
    return queue;
}

// -------------------------------------------------------------------------

void Cyg_ThreadQueue_Implementation::remove(Cyg_Thread *thread)
{
    CYG_REPORT_FUNCTION();
        
    // If the thread we want is the at the head
    // of the list, and is on its own, clear the
    // list and return. Otherwise advance to the
    // next thread and remove ours. If the thread
    // is not at the head of the list, just dequeue
    // it.

    thread->queue = NULL;
    
    if( queue == thread )
    {
        if( thread->next == thread )
        {
            queue = NULL;
            return;
        }
        else queue = thread->next;
    }

    thread->Cyg_SchedThread_Implementation::remove();

}

// -------------------------------------------------------------------------
// Rotate the front thread on the queue to the back.

void Cyg_ThreadQueue_Implementation::rotate()
{
    CYG_REPORT_FUNCTION();
        
    queue = queue->next;
}

// -------------------------------------------------------------------------

#endif

// -------------------------------------------------------------------------
// EOF sched/lottery.cxx
