/*
 *  lil: Linux Interrupt Latency benchmark
 *  Copyright (C) 1998  Andrea Arcangeli
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Andrea Arcangeli can be contacted via email at arcangeli@mbox.queen.it
 *
 *  $Id: lil.c,v 1.9 1998/08/18 18:42:25 andrea Exp $
 */

#include <linux/module.h>
#include <linux/modversions.h>
#include <linux/stddef.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/bitops.h>
#include <asm/signal.h>

/* #define DEBUG 1 */

#define CYCLES(x) __asm__ __volatile__ ("rdtsc" : "=A" (x))

MODULE_PARM(irq, "i");
MODULE_PARM(num, "i");
MODULE_PARM(verbose, "i");

static int irq = -1, num = 1, verbose = 0;
static volatile int semaphore = 0;
#define IRQ	0
#define TIMEOUT	1
static unsigned long long cycles[2];

static void lil_timeout(unsigned long arg);

struct timer_list lil_timer =
{
    NULL, NULL, 0, 0, lil_timeout
};

static void lil_timeout(unsigned long arg)
{
	set_bit(TIMEOUT, &semaphore);
}

static void gen_irq(void)
{
	cli();
	/*
	 * Here is the fun. We must try to generate an hardware irq.
	 * We can do that triggering the edge-level register of the 8259 irq
	 * controller.
	 */
	if (irq & 8)
		outb(1 << (irq & 7), 0x4d1);
	else
		outb(1 << irq, 0x4d0);

	CYCLES(cycles[0]);
	sti();
}

static void clear_irq(void)
{
	/*
	 * This is not the best but normally the edge/level registers
	 * must be set to 0 so it should be safe enough.
	 */
	if (irq & 8)
		outb(0, 0x4d1);
	else
		outb(0, 0x4d0);
}

static void lil_irq(int hard_irq, void *data, struct pt_regs *regs)
{
	CYCLES(cycles[1]);
	set_bit(IRQ, &semaphore);
	/*
	 * It' s very important to clear the irq. Without this line
	 * the irq will never been released and the machine will lock.
	 */
	clear_irq();
}

unsigned long trimmer_time(void)
{
	unsigned long long time[2];
	unsigned int i;

	/*
	 * Put it in L1 cache.
	 */
	for (i=0; i<100; i++)
	{
		CYCLES(time[0]);
		CYCLES(time[1]);
	}

	return time[1] - time[0];
}

static unsigned long sample_irq(unsigned long wasted_time)
{

	/*
	 * Set the timer.
	 */
	lil_timer.expires = jiffies + HZ/10;
	add_timer(&lil_timer);

	/*
	 * Produce the irq.
	 */
	gen_irq();

	/*
	 * Wait for the irq or for the timeout.
	 */
	while (!semaphore);

	/*
	 * Remove the timeout here to be sure to not leave timer on line.
	 */
	del_timer(&lil_timer);

	if (test_bit(TIMEOUT, &semaphore))
	{
		printk("can' t generate an hardware irq on the line %d\n",
		       irq);
		return 0;
	}
	if (!test_bit(IRQ, &semaphore))
	{
		printk("bug detected, semaphore = %x\n", semaphore);
		return 0;
	}
	return cycles[1] - cycles[0] - wasted_time;
}

static void irq_train(void)
{
	int i;
	unsigned long latency, max = 0, min = -1, mean = 0;
	unsigned long wasted_time = trimmer_time();

	for (i=1; i<=num; i++)
	{
		latency = sample_irq(wasted_time);
		if (verbose)
			printk("irqN%4d latency: %4ld\n", i, latency);
		if (!latency)
			return;
		if (latency > max)
			max = latency;
		if (latency < min)
			min = latency;
		if (i == 1)
			mean = latency;
		else
		{
			mean *= i - 1;
			mean += latency;
			mean /= i;
		}
		schedule();
	}
	printk("latency cycles: mean %lu, max %lu, min %lu\n",
	       mean, max, min);
}

int init_module(void)
{
	unsigned int retval;

	printk("Linux Interrupt Latency benchmark - "
	       "Copyright (C) 1998  Andrea Arcangeli\n");

	if (irq < 0 || irq > 15)
	{
		printk("You must load the module specifying the irq line,"
		       " for example:\n"
		       "# insmod ./lil.o irq=3 num=1 verbose=0\n");
		return 1;
	}

	retval = request_irq(irq, lil_irq, SA_INTERRUPT, "lil", NULL);
	if (retval)
	{
		printk("irq %d is busy\n", irq);
		return 1;
	}

	irq_train();

	free_irq(irq, NULL);

	return 1;
}
