/*
 * POSIX.4 Timers 
 *
 * Written by J. Vidal, F. Vazquez
 * Copyright (C) Dec, 2002 OCERA Consortium.
 *
 * This program 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.
 *
 */
 
#include <time.h>

#ifdef _RTL_POSIX_TIMERS
#define _RTL_POSIX_TIMERS_DEBUG 

/* Timer List Manipulation. */ 
struct rtl_timer_struct *timer_list_start=NULL;
spinlock_t rtl_timer_list_lock;

int del_rtl_timer_from_list(struct rtl_timer_struct **start,timer_t timer);
int add_rtl_timer_to_list(struct rtl_timer_struct *new_element,struct rtl_timer_struct **start);

/* POSIX.4 Timers API.*/
/* One-Shot and Repeating Timers */ 
int timer_create(clockid_t clock_id, const struct sigevent *signal_specification, timer_t *timer_ptr){
  static int id=0;
  
  if ((pthread_self() != pthread_linux())) {
errno=EINVAL;
    return -1;

  }
  // for now, only CLOCK_REALTIME & CLOCK_MONOTONIC. 
  if (clock_id != CLOCK_REALTIME && clock_id != CLOCK_MONOTONIC){
    errno=EINVAL;
    return -1;
  }

  *timer_ptr=(timer_t) kmalloc (sizeof(struct rtl_timer_struct), GFP_KERNEL);
  
  if (!(*timer_ptr))
    { 
      rtl_printf("Can't allocate memory for timer %d\n",id); 
      errno= EAGAIN;
      return -1; 
    }

  (*timer_ptr)->id=id++;
  (*timer_ptr)->clock_id=clock_id;
  
  if (signal_specification){
    (*timer_ptr)->signal.sigev_notify=signal_specification->sigev_notify;
    if (signal_specification->sigev_notify == SIGEV_SIGNAL) {
      if (signal_specification->sigev_signo >= RTL_SIGUSR1 && signal_specification->sigev_signo <= RTL_MAX_SIGNAL){
	(*timer_ptr)->signal.sigev_signo=signal_specification->sigev_signo;
      }else {
	errno=EINVAL;
	return -1;
      }
    }
  } else {
    (*timer_ptr)->signal.sigev_signo=0;
  }
    
  (*timer_ptr)->owner=NULL; 
  (*timer_ptr)->expires.it_interval=0;
  (*timer_ptr)->expires.it_value=0;
  
  (*timer_ptr)->magic= RTL_TIMER_MAGIC;
  
  return 0; 
}
 
int timer_delete(timer_t timer_id){

  if (!timer_id) {
    errno=EINVAL;
    return -1;
  }
  
#ifdef _RTL_POSIX_TIMERS_DEBUG 
  if  (!CHECK_VALID_TIMER(timer_id)){
    errno=EINVAL;
    return -1;
  }
#endif
  
  if ((pthread_self() != pthread_linux())) {
    return EAGAIN;
  }

  timer_id->magic=0;

  // Protect timer list from writes,
  rtl_spin_lock (&rtl_timer_list_lock);
  del_rtl_timer_from_list(get_ptr_to_timer_list_start(),timer_id);
  rtl_spin_unlock (&rtl_timer_list_lock);
    
  kfree(timer_id);

  return 0;

}

int timer_settime(timer_t timer_id, int flags, const struct itimerspec *new_setting, struct itimerspec *old_setting){
  hrtime_t now=0;
  
  if (!timer_id){    
    errno=EINVAL;
    return -1;
  }
  
#ifdef _RTL_POSIX_TIMERS_DEBUG 
  if (!CHECK_VALID_TIMER(timer_id)){    
    errno=EINVAL;
    return -1;
  }
#endif
  
  if (old_setting){
    timer_gettime(timer_id,old_setting);
  }
  // Valid specs ?
  if (!new_setting || 
      new_setting->it_value.tv_nsec < 0 || new_setting->it_value.tv_nsec >=NSECS_PER_SEC ||
      new_setting->it_interval.tv_nsec < 0 || new_setting->it_interval.tv_nsec >=NSECS_PER_SEC ||
      new_setting->it_value.tv_sec < 0 ||
      new_setting->it_interval.tv_sec < 0 ){

    errno=EINVAL; 
    return -1;
  }
  
  /* 
     Check that it_value is different from 0, to
     program the timer. IF both are 0 timer is disarmed.
  */
  if (new_setting->it_value.tv_sec || new_setting->it_value.tv_nsec) 
    {      
      // If the expiration time (it_value) is relative, convert it to absolute.
      if (!(flags & TIMER_ABSTIME)){
	now=clock_gethrtime(timer_id->clock_id);
      }
      
      timer_id->expires.it_value=now+timespec_to_ns(&new_setting->it_value);
      timer_id->expires.it_interval=timespec_to_ns(&new_setting->it_interval);
      
      /*
	Both it_value.tv_sec & it_value.tv_nsec are 0,
	so disarm timer.
      */ 
    }else{
      DISARME_TIMER(timer_id);
      return 0;
    }

  //set timer owner and reorder timer list if needed.
  if (timer_id->owner!=pthread_self()){
    // Protect timer_list from writes,
    rtl_spin_lock (&rtl_timer_list_lock);

    /* if the timer had already an owner, delete from timer list and ...*/
    if (timer_id->owner)
      del_rtl_timer_from_list(get_ptr_to_timer_list_start(),timer_id);

    timer_id->owner=pthread_self(); 
    /* insert it ordered by the new owner priority. */
    add_rtl_timer_to_list(timer_id, get_ptr_to_timer_list_start());
    rtl_spin_unlock (&rtl_timer_list_lock);
    
  } 
  
  /* For now, the schedule is called allways */
   clear_bit (RTL_SCHED_TIMER_OK, &sched_data((timer_id->owner)->cpu)->sched_flags);
   rtl_schedule();
  return 0;
}

int timer_gettime(timer_t timer_id, struct itimerspec *ts_set)
{  
if (!timer_id || !ts_set){    
    errno=EINVAL;
    return -1;
  }
  
#ifdef _RTL_POSIX_TIMERS_DEBUG 
  if (!CHECK_VALID_TIMER(timer_id)){    
    errno=EINVAL;
    return -1;
  }
#endif

  if (TIMER_ARMED(timer_id)){     
    ts_set->it_value=timespec_from_ns((long long)(timer_id->expires.it_value - clock_gethrtime(timer_id->clock_id)));
  } else {
    ts_set->it_value.tv_sec=0;
    ts_set->it_value.tv_nsec=0;
  }

  ts_set->it_interval= timespec_from_ns( timer_id->expires.it_interval);
  return 0;
}  
   

int timer_getoverrun(timer_t timer_id)
{
  return EINVAL;
}


/* Timer List Manipulation. */ 
/* Removes a timer from the timer list*/
int del_rtl_timer_from_list(struct rtl_timer_struct **start,timer_t timer){
  struct rtl_timer_struct *p=*start , *prev=NULL;

  while (p != NULL) {
    if (p->id == timer->id)
      break;

    prev = p;
    p = p->next;
  }

  if (p == NULL) {
    return -1;/* not found */
  }

  if (prev == NULL) {/* deletion of list's head */
    *start = p->next;
  }
  else {
    prev->next = p->next;
  }
  return 0;
}

/* Adds a new timer to the list,
   maintainig it ordered by thread
   owner priority. */
int add_rtl_timer_to_list(struct rtl_timer_struct *newtimer,struct rtl_timer_struct **start)
{
  struct rtl_timer_struct *p=*start, *prev=NULL;

#ifdef _RTL_POSIX_TIMERS_DEBUG
  if (!CHECK_VALID_TIMER(newtimer)) {
    rtl_printf("PANIC in add_rtl_timer_to_list: newtimer not a valid timer !\n");
    return -1;
  }

#endif

  /* find the right place to put new timer */
  while (p != NULL) {
    if (RTL_PRIO(newtimer->owner) > RTL_PRIO(p->owner)){/* we have found the place */
      break;
    }

    prev = p;
    p = p->next;
  }

  if (prev == NULL) {/* First element */
    newtimer->next = *start; /* append list */
    *start = newtimer; /* new item becomes head of list */
  }
  else {/* is in the middle */
    newtimer->next = p;
    prev->next = newtimer;
  }
    
  return 0;
}

#endif



