/* ==== 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;

/* ==========================================================================
 * 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()
{
	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;
}

/* ==========================================================================
 * 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);
}

/* ==========================================================================
 * 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.tv_sec 	= delta_time.tv_sec;
				current_time.tv_nsec 	= delta_time.tv_nsec;
				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);
}
