/*
 * A module using sysctl to configure a  busy looping thread.
 * Note that the part related to sysctl is at the end.
 *
 * Tested with 2.0.24 (sparc) 2.0.27 (alpha) 2.0.30, 2.1.32, 2.1.43 (intel) 
 * On the Alpha the symbol __kernel_thread must be exported by the
 * kernel if you want to load this module.i
 *
 * Copyright 1995-1997 Stephen Tweedie (the intvec part)
 * Copyright 1997      Alessandro Rubini (the rest and the bugs)
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 ********/

#define __KERNEL__
#define MODULE
#include <linux/module.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/sysctl.h>
#include <linux/ctype.h>

#if LINUX_VERSION_CODE < 0x020100 /* 2.0 or earlier */
#include <asm/segment.h>
#else
#include <asm/uaccess.h>
#endif

#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>  /* kernel_thread() */
#include <asm/unistd.h>

#ifndef VERSION_CODE
#  define VERSION_CODE(vers,rel,seq) ( (vers<<16) | (rel<<8) | seq )
#endif

#if LINUX_VERSION_CODE < VERSION_CODE(2,1,8)
extern int proc_dointvec(ctl_table *table, int write, struct file *filp,
                  void *buffer, size_t *lenp);
extern int sysctl_intvec(ctl_table *table, int *name, int nlen,
                void *oldval, size_t *oldlenp,
                void *newval, size_t newlen, void **context);
#endif

#if LINUX_VERSION_CODE < VERSION_CODE(2,1,23)
#  define INCRCOUNT(p)  ((p)->use_count++)
#  define CURRCOUNT(p)  ((p)->use_count)
#  define DECRCOUNT(p)  ((p)->use_count--)
#else
#  define INCRCOUNT(p)  ((p)->module ? __MOD_INC_USE_COUNT((p)->module) : 0)
#  define CURRCOUNT(p)  ((p)->module && (p)->module->usecount)
#  define DECRCOUNT(p)  ((p)->module ? __MOD_DEC_USE_COUNT((p)->module) : 0)
#endif

extern int busy_ontime;   /* loop 0 ticks */
extern int busy_offtime; /* every second */

struct wait_queue *busy_wait;

int busy_pid;

int busy_thread(void *unused)
{
    struct task_struct *p;
    /* prepare the thread so it looks good (make it real-time by now) */
    current->session = 1;
    current->pgrp = 1;
    current->policy = SCHED_RR; /* real-time, round-robin */
    current->rt_priority = 0;
    sprintf(current->comm,"busy");

    /* what we need now is disassociate from userland */

    /*
     * If the mm structure is NULL, do_exit will oops().  The only mm
     * which is not freed is init_mm, which is static. the symbol is
     * not public, so assign current->mm to the one of process 2
     * (kflushd).
     */
    for (p = current ; (p = p->next_task) != current ; )
        if (p->pid == 2)
            break;
    current->mm->count--; /* detatch */
    current->mm = p->mm;

    /* don't attach, as init_mm doesn't use the count */

    current->fs->count--; /* incremented by CLONE_FS */
    current->fs=NULL;
    current->files->count--; /* incremented by CLONE_FILES */
    current->files=NULL;
#if 0
    /* can't zero current->sig, otherwise I can't be killed */
    current->sig->count; /* incremented by CLONE_SIGHAND */
    current->sig=NULL;
#endif

    if (current->exec_domain && CURRCOUNT(current->exec_domain)) {
        DECRCOUNT(current->exec_domain);
	}
    if (current->binfmt && CURRCOUNT(current->binfmt))
      DECRCOUNT(current->binfmt);

    /* ------------------------------------ */
    /* This is the actual job of the thread */
    /* ------------------------------------ */

    while (1) {
        unsigned long j;

        /* first loop */
        j = jiffies;
        while (jiffies < j + busy_ontime)
            /* nothing */ ;

        /* then sleep */
        current->timeout = jiffies + busy_offtime;
        interruptible_sleep_on(&busy_wait);
        if (current->signal & ~current->blocked) /* exit on any signal */
            break;

    }
    printk(KERN_INFO "busy: thread killed\n");
    return 0;
}

void busy_loop(void)
{
    busy_pid = kernel_thread(busy_thread, NULL,
                  CLONE_PID|CLONE_FS|CLONE_VM|CLONE_FILES|CLONE_SIGHAND);
    printk(KERN_INFO "busy: the thread has pid = %i\n",busy_pid);
}


/* =================================================================== */
/*               The part related to sysctl begins here                */

int busy_ontime = 0;   /* loop 0 ticks */
int busy_offtime = HZ; /* every second */

#define KERN_BUSY 434 /* a random number, high enough */
enum {BUSY_ON=1, BUSY_OFF};

/* two integer items */
static ctl_table busy_table[] = {
	{BUSY_ON, "ontime", &busy_ontime, sizeof(int), 0644,
	NULL, &proc_dointvec, &sysctl_intvec, /* fill with 0's */},
	{BUSY_ON, "offtime", &busy_offtime, sizeof(int), 0644,
	NULL, &proc_dointvec, &sysctl_intvec, /* fill with 0's */},
	{0}
	};

/* a directory */
static ctl_table busy_kern_table[] = {
	{KERN_BUSY, "busy", NULL, 0, 0555, busy_table},
	{0}
	};

/* the kernel directory */
static ctl_table busy_root_table[] = {
	{CTL_KERN, "kernel", NULL, 0, 0555, busy_kern_table},
	{0}
	};

static struct ctl_table_header *busy_table_header;


int init_module(void) 
{
	busy_table_header = register_sysctl_table(busy_root_table, 0);
	if (!busy_table_header)
		return -ENOMEM;
	busy_loop();
	return 0;
}

/* this function looks for busy_thread in the active processes */
struct task_struct *busy_findthread(void)
{
	struct task_struct *p;

	for (p = current ; (p = p->next_task) != current ; )
		if (p->pid == busy_pid)
			break;
	if (p->pid == busy_pid)
		return p;
	return NULL;
}

void cleanup_module(void)
{
	struct task_struct *p = busy_findthread();

	if (!p) {
		printk(KERN_INFO "can't find pid %i, will surely oops...\n",
		       busy_pid);
		return;
	}
	while (1) { /* wait for the thread to exit */
		send_sig(SIGKILL, p, 1);
		schedule();
		p = busy_findthread();
		if (!p)
			break;
		printk(KERN_INFO "busy (unload): waiting...\n");
	}

	unregister_sysctl_table(busy_table_header);
}


/* ======================================= */
/* This part comes from the kernel sources (version 2.0) */

#if LINUX_VERSION_CODE < VERSION_CODE(2,1,8)

int proc_dointvec(ctl_table *table, int write, struct file *filp,
		  void *buffer, size_t *lenp)
{
	int *i, vleft, first=1, len, left, neg, val;
	#define TMPBUFLEN 20
	char buf[TMPBUFLEN], *p;
	
	if (!table->data || !table->maxlen || !*lenp ||
	    (filp->f_pos && !write)) {
		*lenp = 0;
		return 0;
	}
	
	i = (int *) table->data;
	vleft = table->maxlen / sizeof(int);
	left = *lenp;
	
	for (; left && vleft--; i++, first=0) {
		if (write) {
			while (left && isspace(get_user((char *) buffer)))
				left--, ((char *) buffer)++;
			if (!left)
				break;
			neg = 0;
			len = left;
			if (len > TMPBUFLEN-1)
				len = TMPBUFLEN-1;
			memcpy_fromfs(buf, buffer, len);
			buf[len] = 0;
			p = buf;
			if (*p == '-' && left > 1) {
				neg = 1;
				left--, p++;
			}
			if (*p < '0' || *p > '9')
				break;
			val = simple_strtoul(p, &p, 0);
			len = p-buf;
			if ((len < left) && *p && !isspace(*p))
				break;
			if (neg)
				val = -val;
			buffer += len;
			left -= len;
			*i = val;
		} else {
			p = buf;
			if (!first)
				*p++ = '\t';
			sprintf(p, "%d", *i);
			len = strlen(buf);
			if (len > left)
				len = left;
			memcpy_tofs(buffer, buf, len);
			left -= len;
			buffer += len;
		}
	}

	if (!write && !first && left) {
		put_user('\n', (char *) buffer);
		left--, buffer++;
	}
	if (write) {
		p = (char *) buffer;
		while (left && isspace(get_user(p++)))
			left--;
	}
	if (write && first)
		return -EINVAL;
	*lenp -= left;
	filp->f_pos += *lenp;
	return 0;
}

int sysctl_intvec(ctl_table *table, int *name, int nlen,
		void *oldval, size_t *oldlenp,
		void *newval, size_t newlen, void **context)
{
	int i, length, *vec, *min, *max;

	if (newval && newlen) {
		if (newlen % sizeof(int) != 0)
			return -EINVAL;

		if (!table->extra1 && !table->extra2)
			return 0;

		if (newlen > table->maxlen)
			newlen = table->maxlen;
		length = newlen / sizeof(int);

		vec = (int *) newval;
		min = (int *) table->extra1;
		max = (int *) table->extra2;

		for (i = 0; i < length; i++) {
			int value = get_user(vec + i);
			if (min && value < min[i])
				return -EINVAL;
			if (max && value > max[i])
				return -EINVAL;
		}
	}
	return 0;
}

#endif /* 2.1.8 */

