/*
 *  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.7 1998/08/17 13:32:46 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 CYCLES(x) __asm__ __volatile__ ("rdtsc" : "=a" (x) : : "edx")

MODULE_PARM(irq, "i");

static int irq = -1;
static volatile int semaphore = 0;
#define IRQ	0
#define TIMEOUT	1
static unsigned 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 lil_irq(int hard_irq, void *data, struct pt_regs *regs)
{
	CYCLES(cycles[1]);
	set_bit(IRQ, &semaphore);
	if (irq & 8)
		outb(0, 0x4d1);
	else
		outb(0, 0x4d0);
}

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 & ~8), 0x4d1);
	else
		outb(1 << irq, 0x4d0);

	/*
	 * Wait for a while to be sure that the interrupt is detected from
	 * the CPU istantaneously after the sti().
	 */
	udelay(1000);

	CYCLES(cycles[0]);
	sti(); /* run the irq for real */
}

int init_module(void)
{
	unsigned int retval;

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

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

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

	if (test_bit(TIMEOUT, &semaphore))
	{
		printk("can' t generate an hardware irq on the line %d\n",
		       irq);
		goto out;
	}
	if (!test_bit(IRQ, &semaphore))
	{
		printk("bug detected, semaphore = %x\n", semaphore);
		goto out;
	}

	printk("irq latency cycles = %ld\n", cycles[1] - cycles[0]);

 out:
	del_timer(&lil_timer);
	free_irq(irq, NULL);
	return 1;
}
