/*
 * rt_process.c: periodic thread and miscellaneous real-time support
 *
 * Copyright (C) 2000 TimeSys Corporation
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */
#include <rk/rk_linux.h>
#include <rk/rk_error.h>
#include <rk/rk.h>
#include <rk/timespec.h>
#include <linux/time.h>
#include <asm/processor.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <asm/uaccess.h>

extern rk_timer_t timer_create(void);

/* the shortest period that we aim to support */
static struct timespec MIN_PERIOD = { 0, 500000 };

static void period_start_handler(rk_timer_t tmr)
{
    rk_reserve_t rsv;
    cpu_reserve_t cpu;
    cpu_tick_data_t now = tmr->tmr_expire;
    struct timespec tnow;
    int i = 0;

    /* CBS arrival rule... By Luca */
    rsv = tmr->tmr_rsv;
    if (rsv != NULL) {
	if (tmr->tmr_type == TMR_NEXT_PERIOD) {
	    cpu = rsv->rsv_rsv;
	    if (cpu == NULL) {
		printk("Acc!!!\n");
	    } else {
		tnow.tv_sec = TICK2USEC(&now) / 1000000;
		tnow.tv_nsec = (TICK2USEC(&now) % 1000000) * 1000;
		while (timespec_ge(tnow, cpu->absdl) && (i < 10)) {

		    timespec_add(cpu->absdl, cpu->cpu_res_attr.deadline);
		    cpu->cpu_eligible_deadline_ticks +=
			cpu->cpu_deadline_ticks;
		    i++;
		}
		/* Alternative:
		 * if timespec_ge(tnow, cpu->absdl) {
		 *      timespec_set(cpu->absdl, tnow);
		 *      timespec_add(cpu->absdl, cpu->cpu_res_attr.deadline);
		 *      }
		 */

		/* Well... Let's be fair!!! We also have to recharge the CBS
		   budget... */
		cpu->cpu_period_used_ticks = 0;
	    }
	}
    }

    tmr->tmr_expire += tmr->period;
    tmr->tmr_type = TMR_BUFF_PERIOD;
    rk_timer_add(tmr);
    wake_up(&tmr->wq);
}

static void next_period_handler(rk_timer_t tmr)
{
    /* TODO: check if process is still running and, if so, tell
       it that it missed its deadline. */

    tmr->tmr_expire += tmr->period;
    rk_timer_add(tmr);
    wake_up(&tmr->wq);
}

asmlinkage int
sys_rt_make_periodic(struct timespec *_period, struct timespec *_start)
{
    struct timespec period, start, curr;
    unsigned long flags;
    rk_resource_set_t rs;
    unsigned long int usnow;

    copy_from_user_ret(&period, _period, sizeof(struct timespec), -EFAULT);
    copy_from_user_ret(&start, _start, sizeof(struct timespec), -EFAULT);

    if (current->periodicType == PERIODIC)
	return -EINVAL;
    if (timespec_ge(period, MIN_PERIOD)) {
	rk_timer_t tmr;
	cpu_tick_data_t nanos, now;
	struct timespec cur_time;

	/* allocate a timer */
	tmr = rk_timer_create();
	current->period_timer = tmr;
	current->periodicType = PERIODIC;
	current->period = period;
	current->startTime = start;
	tmr->tmr_type = TMR_PERIOD_START;

	tmr->tmr_handler = period_start_handler;

	if (current->rk_resource_set != NULL) {
	    rs = current->rk_resource_set;
	    tmr->tmr_rsv = rs->rs_cpu;
	}
	init_waitqueue(&tmr->wq);

	rk_cur_time(&cur_time);
	rk_rdtsc(&now);
	timespec_sub(start, cur_time);

	if (start.tv_sec < 0)
	    start.tv_sec = start.tv_nsec = 0;

	nanos = timespec2nano(start);
	nanosec2tick(&nanos, &tmr->tmr_expire);
	nanos = timespec2nano(period);
	nanosec2tick(&nanos, &tmr->period);
	tmr->tmr_expire += now;

	timespec_set(current->last_start, start);
	usnow = TICK2USEC(&now);
	curr.tv_sec = usnow / 1000000;
	curr.tv_nsec = (usnow % 1000000) * 1000;
	timespec_add(current->last_start, curr);

	if (tmr->tmr_expire < now) {
	    tmr->tmr_type = TMR_NEXT_PERIOD;
	    tmr->tmr_expire = now + tmr->period;
	    timespec_set(current->last_start, period);
	    timespec_add(current->last_start, curr);
	}

	rk_spin_lock(flags);
	rk_timer_add(tmr);
	rk_spin_unlock(flags);

	return RK_SUCCESS;
    }
    return -EINVAL;
}

asmlinkage int sys_rt_wait_for_start_time(void)
{
    cpu_tick_data_t now, one_us;
    unsigned long usnow;

    if (current->periodicType != PERIODIC)
	return -EPERM;

    one_us = 1000;
    nanosec2tick(&one_us, &one_us);
    rk_rdtsc(&now);

    usnow = TICK2USEC(&current->period_timer->tmr_expire);
    current->last_start.tv_sec = usnow / 1000000;
    current->last_start.tv_nsec = (usnow % 1000000) * 1000;

    if (current->period_timer->tmr_type != TMR_PERIOD_START ||
	now + one_us > current->period_timer->tmr_expire) {
	/* Process will start very soon (less than 1 microsecond from now),
	   or has already started, so do a busy wait to avoid a race condition
	   if the timer goes off between the time check and finally being put
	   to sleep. */

	while (1) {
	    rk_rdtsc(&now);
	    if (current->period_timer->tmr_type != TMR_PERIOD_START ||
		now >= current->period_timer->tmr_expire)
		return RK_SUCCESS;
	}
    }

    interruptible_sleep_on(&current->period_timer->wq);
    return RK_SUCCESS;
}

asmlinkage int sys_rt_wait_for_next_period(void)
{
    unsigned long int usnow;
    struct timespec next_timer;

    if (current->periodicType != PERIODIC)
	return -EPERM;

    usnow = TICK2USEC(&current->period_timer->tmr_expire);
    next_timer.tv_sec = usnow / 1000000;
    next_timer.tv_nsec = (usnow % 1000000) * 1000;
    timespec_add(current->last_start, current->period);

    if (timespec_gt(next_timer, current->last_start)) {
	current->period_timer->tmr_type = TMR_BUFF_PERIOD;

	return RK_SUCCESS;
    }


    timespec_set(current->last_start, next_timer);

    current->period_timer->tmr_type = TMR_NEXT_PERIOD;
    interruptible_sleep_on(&current->period_timer->wq);
    return RK_SUCCESS;
}

asmlinkage int sys_rt_process_get_period(int pid, struct timespec *period)
{
    if (current->periodicType != PERIODIC)
	return -EPERM;

    if (copy_to_user(period, &(current->period), sizeof(struct timespec)))
	return -EFAULT;

    return RK_SUCCESS;
}

asmlinkage int sys_rt_process_set_period(int pid, struct timespec *period)
{
    cpu_tick_data_t nanos;
    if (current->periodicType != PERIODIC)
	return -EPERM;

    if (copy_from_user
	(&(current->period), period,
	 sizeof(struct timespec))) return -EFAULT;

    nanos = timespec2nano(current->period);
    nanosec2tick(&nanos, &current->period_timer->period);

    return RK_SUCCESS;
}

asmlinkage int sys_rt_set_scheduling_policy(int policy)
{
    /* NOT supported yet but will be soon! */
    return -EINVAL;
}

extern long cpu_hz;
asmlinkage unsigned long sys_rt_get_clock_frequency(void)
{
    return rk_cpu_ticks_per_jiffy * HZ;
}

int sys_rt_get_scheduling_error(void)
{
    rk_resource_set_t rs;
    rk_reserve_t rsv;
    cpu_reserve_t cpu;
    struct timespec err;
    int l;

    rs = current->rk_resource_set;
    if (rs == NULL) {
	return -1;
    }

    rsv = rs->rs_cpu;
    if (rsv == 0) {
	return -1;
    }
    cpu = rsv->rsv_rsv;

/*
printk("[0x%p] dl %ld %ld st %ld %ld\n", rsv,
		cpu->absdl.tv_sec, cpu->absdl.tv_nsec,
		current->last_start.tv_sec, current->last_start.tv_nsec);
*/
    timespec_set(err, cpu->absdl);
    timespec_sub(err, current->last_start);
    timespec_sub(err, cpu->cpu_res_attr.period);
    if (err.tv_sec < 0) {
	err.tv_sec = 0;
	err.tv_nsec = 0;
    }
    l = timespec2micro(err);

    return l;
}
