/* ==== sleep.c ============================================================
 * Copyright (c) 1993, 1994 by Chris Provenzano, proven@mit.edu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *  This product includes software developed by Chris Provenzano.
 * 4. The name of Chris Provenzano may not be used to endorse or promote 
 *	  products derived from this software without specific prior written
 *	  permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CHRIS PROVENZANO ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL CHRIS PROVENZANO BE LIABLE FOR ANY 
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 *
 * Description : All the appropriate sleep routines.
 *
 *  1.00 93/12/28 proven
 *      -Started coding this file.
 *
 *	1.36 94/06/04 proven
 *		-Use new timer structure pthread_timer, that uses seconds
 *		-nano seconds. Rewrite all routines completely.
 *
 *	1.38 94/06/13 proven
 *		-switch pthread_timer to timespec
 */

#ifndef lint
static const char rcsid[] = "sleep.c,v 1.1 1995/06/11 00:21:41 hjl Exp";
#endif

#include <sys/time.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#ifdef __linux__
#include <pthread/mit/sys/compat.h>
#else
#include <sys/compat.h>
#endif

struct pthread * pthread_sleep = NULL;

extern void sig_handler_fake          (int);
extern void pthread_resched_resume    (enum pthread_state);
extern void pthread_sched_resume    (void);

/*
 * Prototypes.
 */
static int     sleep_compare_time   (struct timespec *, struct timespec *);
void           machdep_stop_timer   (struct timespec *);
int            machdep_start_timer  (struct timespec *, struct timespec *);
void           sleep_schedule       (struct timespec *, struct timespec *);
int            sleep_wakeup         (void);
void           __sleep              (struct timespec *);
void           usleep               (unsigned long);
int            sleep_cancel         (struct pthread *);
unsigned int   sleep                (unsigned int);

/* ==========================================================================
 * sleep_compare_time()
 */
/* static inline int sleep_compare_time(struct timespec * time1, 
   struct timespec * time2) */
static int sleep_compare_time(struct timespec * time1, struct timespec * time2)
{
  if ((time1->tv_sec < time2->tv_sec) || 
      ((time1->tv_sec == time2->tv_sec) &&
       (time1->tv_nsec < time2->tv_nsec))) {
    return(-1);
  }
  if ((time1->tv_sec == time2->tv_sec) && (time1->tv_nsec == time2->tv_nsec)){
    return(0);
  }
  return(1);
}

/* ==========================================================================
 * machdep_stop_timer()
 *
 * Returns the time left on the timer.
 */
static struct itimerval timestop = { { 0, 0 }, { 0, 0 } };

void machdep_stop_timer(struct timespec *current)
{
  struct itimerval timenow;

  setitimer(ITIMER_REAL, & timestop, & timenow);
  if (current) {
    current->tv_nsec = timenow.it_value.tv_usec * 1000;
    current->tv_sec = timenow.it_value.tv_sec;
  }
}

/* ==========================================================================
 * machdep_start_timer()
 */
int machdep_start_timer(struct timespec *current, struct timespec *wakeup)
{
  struct itimerval timeout;

  timeout.it_value.tv_usec = (wakeup->tv_nsec - current->tv_nsec) / 1000;
  timeout.it_value.tv_sec = wakeup->tv_sec - current->tv_sec;
  timeout.it_interval.tv_usec = 0;
  timeout.it_interval.tv_sec = 0;
  if (timeout.it_value.tv_usec < 0) {
    timeout.it_value.tv_usec += 1000000;
    timeout.it_value.tv_sec--;
  }

  if ((!(timeout.it_value.tv_sec < 0)) &&
      ((timeout.it_value.tv_usec) || (timeout.it_value.tv_sec))) {
    setitimer(ITIMER_REAL, & timeout, NULL);
  } else {
    /*
     * There is no time on the timer.
     * This shouldn't happen,
     * but isn't fatal.
     */
    sig_handler_fake(SIGALRM);
  }
  return(OK);
}

/* ==========================================================================
 * sleep_schedule()
 *
 * Assumes that the current thread is the thread to be scheduled
 * and that the kthread is already locked.
 */
void sleep_schedule(struct timespec *current_time, struct timespec *new_time)
{
  struct pthread * pthread_sleep_current, * pthread_sleep_prev;

  /* Record the new time as the current thread's wakeup time. */
  pthread_run->wakeup_time = *new_time;

  /* any threads? */
  if ((pthread_sleep_current = pthread_sleep)) {
    if (sleep_compare_time(&(pthread_sleep_current->wakeup_time),
                           new_time) <= 0) {
      /* Don't need to restart timer */
      while (pthread_sleep_current->sll) {

        pthread_sleep_prev =  pthread_sleep_current;
        pthread_sleep_current = pthread_sleep_current->sll;

        if (sleep_compare_time(&(pthread_sleep_current->wakeup_time),
                               new_time) > 0) {
          pthread_run->sll = pthread_sleep_current;
          pthread_sleep_prev->sll = pthread_run;
          return;
        }
      } 

      /* No more threads in queue, attach pthread_run to end of list */
      pthread_sleep_current->sll = pthread_run;
      pthread_run->sll = NULL;

    } else {
      /* Start timer and enqueue thread */
      machdep_start_timer(current_time, new_time);
      pthread_run->sll = pthread_sleep_current;
      pthread_sleep = pthread_run;
    }
  } else {
    /* Start timer and enqueue thread */
    machdep_start_timer(current_time, new_time);
    pthread_sleep = pthread_run;
    pthread_run->sll = NULL;
  }
}

/* ==========================================================================
 * sleep_wakeup()
 *
 * This routine is called by the interrupt handler, which has already
 * locked the current kthread. Since all threads on this list are owned
 * by the current kthread, rescheduling won't be a problem.
 */
int sleep_spurious_wakeup = 0;
int sleep_wakeup(void)
{
  struct pthread *pthread_sleep_next;
  struct timespec current_time;
  int ret = 0;

  if (pthread_sleep == NULL) {
    return(NOTOK);
  } 

  machdep_gettimeofday(&current_time);
  if (sleep_compare_time(&(pthread_sleep->wakeup_time), &current_time) > 0) {
    machdep_start_timer(&current_time, &(pthread_sleep->wakeup_time));
    sleep_spurious_wakeup++;
    return(OK);
  }

  do {
    if (pthread_sleep->pthread_priority > ret) {
      ret = pthread_sleep->pthread_priority;
    }

    /*
     * Clean up removed thread and start it running again. 
     *
     * Note: It is VERY important to remove the thread form the
     * current queue before putting it on the run queue.
     * Both queues use pthread_sleep->next, and the thread that points
     * to pthread_sleep should point to pthread_sleep->next then
     * pthread_sleep should be put on the run queue.
     */
    if ((SET_PF_DONE_EVENT(pthread_sleep)) == OK) {
      if (pthread_sleep->queue)
        pthread_queue_remove(pthread_sleep->queue, pthread_sleep);
      pthread_prio_queue_enq(pthread_current_prio_queue, pthread_sleep);
      pthread_sleep->state = PS_RUNNING;
    } 

    pthread_sleep_next = pthread_sleep->sll;
    pthread_sleep->sll = NULL;

    if ((pthread_sleep = pthread_sleep_next) == NULL) {
      /* No more threads on sleep queue */
      return(ret);
    }
  } while (sleep_compare_time(&(pthread_sleep->wakeup_time),
                              &(current_time)) <= 0);

  /* Start timer for next time interval */
  machdep_start_timer(&current_time, &(pthread_sleep->wakeup_time));
  return(ret);
}

/* ==========================================================================
 * __sleep()
 */
void __sleep(struct timespec * time_to_sleep)
{
  struct pthread *pthread_sleep_prev;
  struct timespec current_time, wakeup_time;

  pthread_sched_prevent();

  /* Get real time */
  machdep_gettimeofday(&current_time);
  wakeup_time.tv_sec = current_time.tv_sec + time_to_sleep->tv_sec;
  wakeup_time.tv_nsec = current_time.tv_nsec + time_to_sleep->tv_nsec;

  sleep_schedule(&current_time, &wakeup_time);

  /* Reschedule thread */
  SET_PF_WAIT_EVENT(pthread_run);
  pthread_resched_resume(PS_SLEEP_WAIT);
  CLEAR_PF_DONE_EVENT(pthread_run);

  /* Return actual time slept */
  time_to_sleep->tv_sec = pthread_run->wakeup_time.tv_sec;
  time_to_sleep->tv_nsec = pthread_run->wakeup_time.tv_nsec;
}

#if 0
/* ==========================================================================
 * nanosleep()
 */
unsigned int nanosleep(unsigned int nseconds)
{
  struct timespec time_to_sleep;

  if (nseconds) {
    time_to_sleep.tv_nsec = nseconds;
    time_to_sleep.tv_sec = 0;
    __sleep(&time_to_sleep);
    nseconds = time_to_sleep.tv_nsec;
  }
  return(nseconds);
}
#endif

/* ==========================================================================
 * usleep()
 */
void usleep(unsigned long useconds)
{
  struct timespec time_to_sleep;

  if (useconds) {
    time_to_sleep.tv_nsec = (useconds % 1000000) * 1000;
    time_to_sleep.tv_sec = useconds / 1000000;
    __sleep(&time_to_sleep);
  }
}

/* ==========================================================================
 * sleep()
 */
unsigned int sleep(unsigned int seconds)
{
  struct timespec time_to_sleep;

  if (seconds) {
    time_to_sleep.tv_sec = seconds;
    time_to_sleep.tv_nsec = 0;
    __sleep(&time_to_sleep);
    seconds = time_to_sleep.tv_sec;
  }
  return(seconds);
}

/* ==========================================================================
 * sleep_cancel()
 *
 * Cannot be called while kernel is locked.
 * Does not wake sleeping thread up, just remove it from the sleep queue.
 */
int sleep_cancel(struct pthread * pthread)
{
  struct timespec current_time, delta_time;
  struct pthread * pthread_last;
  int rval = NOTOK;

  /* Lock sleep queue, Note this may be on a different kthread queue */
  pthread_sched_prevent();

  if (pthread_sleep) {
    if (pthread == pthread_sleep) {
      rval = OK;
      machdep_stop_timer(&delta_time);
      if ((pthread_sleep = pthread_sleep->sll)) {
        current_time = delta_time;
        current_time.tv_sec  += pthread_sleep->wakeup_time.tv_sec;
        current_time.tv_nsec += pthread_sleep->wakeup_time.tv_nsec;
        if (current_time.tv_nsec > 1000000000) {
          current_time.tv_nsec -= 1000000000;
          current_time.tv_sec++;
        }
        machdep_start_timer(&(current_time), 
                            &(pthread_sleep->wakeup_time));
      }
    } else {
      for (pthread_last = pthread_sleep; pthread_last;
           pthread_last = pthread_last->sll) {
        if (pthread_last->sll == pthread) {
          pthread_last->sll = pthread->sll;
          rval = OK;
          break;
        }
      }
    }
  }

  pthread_sched_resume();
  pthread->sll = NULL;
  return(rval);
}
