/*
 * RTLinux scheduling accuracy measuring example
 *
 * This modules attempts to reserve a CPU for RTLinux on SMP machines.
 * Note that this feature is only supported on 2.4.x kernels.
 *
 * Written by Michael Barabanov, 2000
 * (C) FSMLabs  2000. baraban@fsmlabs.com
 * Released under the GNU GENERAL PUBLIC LICENSE Version 2, June 1991
 * Any use of this code must include this notice.
 */

#include <rtl.h>
#include <rtl_fifo.h>
#include <time.h>
#include <rtl_sched.h>
#include <rtl_sync.h>
#include <pthread.h>
#include <unistd.h>
#include <rtl_debug.h>
#include <errno.h>
#include "common.h"

int ntests=500;
int period=1000000;
int fifo_size=4000;
int advance=0;
int nolinux=1;

MODULE_PARM(period,"i");
MODULE_PARM(ntests,"i");
MODULE_PARM(advance,"i");
MODULE_PARM(nolinux,"i");

static pthread_t thread;
static int fd_fifo;

static void *thread_code(void *param);

static int reserved;

int init_module(void)
{
	pthread_attr_t attr;
	struct sched_param sched_param;
	int ret;
	int cpu;

	rtf_destroy(0);
	rtf_create(0, fifo_size);

	cpu = rtl_getcpuid();

	rtl_printf("RTLinux measurement module on CPU %d\n",rtl_getcpuid());
	pthread_attr_init (&attr);
	sched_param.sched_priority = 1;

	reserved = 0;

	if (nolinux && rtl_num_cpus() > 1) {
		ret = rtl_reserve_cpus(1 << cpu);
		if (ret) {
			rtl_printf("failed to reserve CPU%d for RTLinux\n", cpu);
		} else {
			rtl_printf("reserved CPU%d for RTLinux\n", cpu);
			reserved = 1;
		}
	}
	pthread_attr_setschedparam (&attr, &sched_param);
	pthread_create (&thread,  &attr, thread_code, (void *)1);
	rtl_printf("created RT-thread\n");

	return 0;
}


void cleanup_module(void)
{
        rtl_printf ("Removing module on CPU %d\n", rtl_getcpuid());
	pthread_delete_np (thread);
	if (reserved) {
		rtl_unreserve_cpus();
	}
	close(fd_fifo);
	rtf_destroy(0);
}


static void *thread_code(void *param) {

	hrtime_t expected;
	hrtime_t diff;
	hrtime_t now;
	hrtime_t min_diff;
	hrtime_t max_diff;
	struct sample samp;
	int i;
	int cnt = 0;
	int cpu_id = rtl_getcpuid();

	rtl_printf ("Measurement task starts on CPU %d\n", cpu_id);

	expected = clock_gethrtime(CLOCK_REALTIME) + 2 * (hrtime_t) period;

	if (advance) {
		pthread_make_periodic_np (pthread_self(), expected - advance, period);
	} else {
		pthread_make_periodic_np (pthread_self(), expected, period);
	}


	fd_fifo = open("/dev/rtf0", O_NONBLOCK);
	if (fd_fifo < 0) {
		rtl_printf("/dev/rtf0 open returned %d\n", fd_fifo);
		return (void *) -1;
	}

	if (advance) {
		rtl_stop_interrupts(); /* Be careful with this! The task won't be preempted by anything else. This is probably only appropriate for small high-priority tasks. */
	}

	do {
		min_diff = 2000000000;
		max_diff = -2000000000;

		for (i = 0; i < ntests; i++) {
			++cnt;
			pthread_wait_np();

			now = clock_gethrtime(CLOCK_MONOTONIC);
			if (advance) {
				if (now < expected) {
					rtl_delay (expected - now);
				}
				now = clock_gethrtime(CLOCK_MONOTONIC);
			}
			diff = now - expected;
			if (diff < min_diff) {
				min_diff = diff;
			}
			if (diff > max_diff) {
				max_diff = diff;
			}

			expected += period;
		}

		samp.min = min_diff;
		samp.max = max_diff;
		write (fd_fifo, &samp, sizeof(samp));
	} while (1);
	return 0;
}
