root/src/add-ons/kernel/bus_managers/firewire/timer.cpp
/* Realtek RTL8169 Family Driver
 * Copyright (C) 2004 Marcus Overhagen <marcus@overhagen.de>. All rights reserved.
 *
 * Permission to use, copy, modify and distribute this software and its 
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies, and that both the
 * copyright notice and this permission notice appear in supporting documentation.
 *
 * Marcus Overhagen makes no representations about the suitability of this software
 * for any purpose. It is provided "as is" without express or implied warranty.
 *
 * MARCUS OVERHAGEN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL MARCUS
 * OVERHAGEN BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <Errors.h>
#include <OS.h>
#include <string.h>

#include "debug.h"
#include "fwdebug.h"
#include "timer.h"

#define MAX_TIMERS 8


struct timer_info
{
        timer_id                id;
        timer_function  func;
        void *                  cookie;
        bigtime_t               next_event;
        bigtime_t               interval;
        bool                    periodic;
};


static struct timer_info        sTimerData[MAX_TIMERS];
static int                                      sTimerCount;
static timer_id                         sTimerNextId;
static thread_id                        sTimerThread;
static sem_id                           sTimerSem;
static spinlock                         sTimerSpinlock;


static int32
timer_thread(void *cookie)
{
        status_t status = 0;

        do {
                bigtime_t timeout;
                bigtime_t now;
                cpu_status cpu;
                timer_function func;
                void * cookie;
                int i;
                int index;

                cpu = disable_interrupts();
                acquire_spinlock(&sTimerSpinlock);

                now = system_time();
                cookie = 0;
                func = 0;
                                
                // find timer with smallest event time
                index = -1;
                timeout = B_INFINITE_TIMEOUT;
                for (i = 0; i < sTimerCount; i++) {
                        if (sTimerData[i].next_event < timeout) {
                                timeout = sTimerData[i].next_event;
                                index = i;
                        }
                }
                
                if (timeout < now) {
                        // timer is ready for execution, load func and cookie
                        ASSERT(index >= 0 && index < sTimerCount);
                        func = sTimerData[index].func;
                        cookie = sTimerData[index].cookie;
                        if (sTimerData[index].periodic) {
                                // periodic timer is ready, update the entry
                                sTimerData[index].next_event += sTimerData[index].interval;
                        } else {
                                // single shot timer is ready, delete the entry
                                if (index != (sTimerCount - 1) && sTimerCount != 1) {
                                        memcpy(&sTimerData[index], &sTimerData[sTimerCount - 1], sizeof(struct timer_info));
                                }
                                sTimerCount--;
                        }
                }

                release_spinlock(&sTimerSpinlock);
                restore_interrupts(cpu);
                
                // execute timer hook
                if (timeout < now) {
                        ASSERT(func);
                        func(cookie);
                        continue;
                }

                status = acquire_sem_etc(sTimerSem, 1, B_ABSOLUTE_TIMEOUT, timeout);
        } while (status != B_BAD_SEM_ID);

        return 0;
}


timer_id
create_timer(timer_function func, void *cookie, bigtime_t interval, uint32 flags)
{
        cpu_status cpu;
        timer_id id;
        
        if (func == 0)
                return -1;
        
        // Attention: flags are not real flags, as B_PERIODIC_TIMER is 3

        cpu = disable_interrupts();
        acquire_spinlock(&sTimerSpinlock);
        
        if (sTimerCount < MAX_TIMERS) {
                id = sTimerNextId;
                sTimerData[sTimerCount].id = id;
                sTimerData[sTimerCount].func = func;
                sTimerData[sTimerCount].cookie = cookie;
                sTimerData[sTimerCount].next_event = (flags == B_ONE_SHOT_ABSOLUTE_TIMER) ? interval : system_time() + interval;
                sTimerData[sTimerCount].interval = interval;
                sTimerData[sTimerCount].periodic = flags == B_PERIODIC_TIMER;
                sTimerNextId++;
                sTimerCount++;
        } else {
                id = -1;
        }
        
        release_spinlock(&sTimerSpinlock);
        restore_interrupts(cpu);
        
        if (id != -1)
                release_sem_etc(sTimerSem, 1, B_DO_NOT_RESCHEDULE);

        return id;
}


status_t
delete_timer(timer_id id)
{
        cpu_status cpu;
        bool deleted;
        int i;

        deleted = false;
        
        cpu = disable_interrupts();
        acquire_spinlock(&sTimerSpinlock);
        
        for (i = 0; i < sTimerCount; i++) {
                if (sTimerData[i].id == id) {
                        if (i != (sTimerCount - 1) && sTimerCount != 1) {
                                memcpy(&sTimerData[i], &sTimerData[sTimerCount - 1], sizeof(struct timer_info));
                        }
                        sTimerCount--;
                        deleted = true;
                        break;
                }
        }
        
        release_spinlock(&sTimerSpinlock);
        restore_interrupts(cpu);

        if (!deleted)
                return B_ERROR;
                
        release_sem_etc(sTimerSem, 1, B_DO_NOT_RESCHEDULE);
        return B_OK;
}


status_t
initialize_timer(void)
{
        sTimerCount = 0;
        sTimerNextId = 1;
        B_INITIALIZE_SPINLOCK(&sTimerSpinlock);
        
        sTimerThread = spawn_kernel_thread(timer_thread, "firewire timer", 80, 0);
        sTimerSem = create_sem(0, "firewire timer");
        set_sem_owner(sTimerSem, B_SYSTEM_TEAM);
        
        if (sTimerSem < 0 || sTimerThread < 0) {
                delete_sem(sTimerSem);
                kill_thread(sTimerThread);
                return B_ERROR;
        }
        
        resume_thread(sTimerThread);
        return B_OK;
}


status_t
terminate_timer(void)
{
        status_t thread_return_value;

        delete_sem(sTimerSem);
        return wait_for_thread(sTimerThread, &thread_return_value);     
}