/*
 * 2007+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * 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.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/moduleparam.h>

static int tw_iterations = 10;
module_param(tw_iterations, int, 0644);

#define TW_TASKLET		1
#define TW_WORKQUEUE		0

static int tw_type = TW_WORKQUEUE;
module_param(tw_type, int, 0644);

static char *tw_type_str[2] = {"workqueue", "tasklet"};

static void tw_tasklet_callback(unsigned long data);
static void tw_work_callback(struct work_struct *work);

static DECLARE_TASKLET(tw_tasklet, tw_tasklet_callback, 0);
static DECLARE_WORK(tw_work, tw_work_callback);
static DECLARE_COMPLETION(tw_completion);

static void tw_tasklet_callback(unsigned long data)
{
	if (--tw_iterations == 0) {
		complete(&tw_completion);
		return;
	}

	tasklet_schedule(&tw_tasklet);
}

static void tw_work_callback(struct work_struct *work)
{
	if (--tw_iterations == 0) {
		complete(&tw_completion);
		return;
	}

	schedule_work(work);
}

static int __init tw_init(void)
{
	struct timeval tv1, tv2;
	long sd, ud;
	int iter = tw_iterations;

	if (tw_type != TW_WORKQUEUE)
		tw_type = TW_TASKLET;

	do_gettimeofday(&tv1);

	if (tw_type == TW_WORKQUEUE)
		schedule_work(&tw_work);
	else
		tasklet_schedule(&tw_tasklet);

	wait_for_completion(&tw_completion);
	
	do_gettimeofday(&tv2);

	if (tw_type == TW_WORKQUEUE)
		flush_scheduled_work();
	else
		tasklet_kill(&tw_tasklet);

	sd = tv2.tv_sec - tv1.tv_sec;
	ud = tv2.tv_usec - tv1.tv_usec;

	printk("type: %s, iterations: %u, diff: %ld.%ld.\n",
			tw_type_str[tw_type], iter, sd, ud);

	return -1;
}

static void __exit tw_exit(void)
{
}

module_init(tw_init);
module_exit(tw_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Evgeniy Polyakov <johnpol@2ka.mipt.ru>");
MODULE_DESCRIPTION("Tasklet/workqueue rescheduling perfomance testing module");
