/*
 *  sched.c - scheduale a function to be called on every timer interrupt.
 *
 *  Copyright (C) 2001 by Peter Jay Salzman
 */

/*
 * The necessary header files
 */

/*
 * Standard in kernel modules
 */
#include <linux/kernel.h>       /* We're doing kernel work */
#include <linux/module.h>       /* Specifically, a module */
#include <linux/proc_fs.h>      /* Necessary because we use the proc fs */
#include <linux/workqueue.h>    /* We scheduale tasks here */
#include <linux/sched.h>        /* We need to put ourselves to sleep
                                   and wake up later */
#include <linux/init.h>         /* For __init and __exit */
#include <linux/interrupt.h>    /* For irqreturn_t */

struct proc_dir_entry *Our_Proc_File;
#define PROC_ENTRY_FILENAME "sched"
#define MY_WORK_QUEUE_NAME "WQsched.c"

/*
 * some work_queue related functions
 * are just available to GPL licensed Modules
 */
MODULE_LICENSE("GPL");

/*
 * The number of times the timer interrupt has been called so far
 */
static int TimerIntrpt = 0;

static void intrpt_routine(struct work_struct *work);

static int die = 0;             /* set this to 1 for shutdown */

/*
 * The work queue structure for this task, from workqueue.h
 */
static struct workqueue_struct *my_workqueue;

static struct delayed_work Task;
static DECLARE_DELAYED_WORK(Task, intrpt_routine);

/*
 * This function will be called on every timer interrupt. Notice the void*
 * pointer - task functions can be used for more than one purpose, each time
 * getting a different parameter.
 */
static void intrpt_routine(struct work_struct *work)
{
    /*
     * Increment the counter
     */
    TimerIntrpt++;

    /*
     * If cleanup wants us to die
     */
    if (die == 0)
        queue_delayed_work(my_workqueue, &Task, 100);
}

/*
 * Put data into the proc fs file.
 */
int
procfile_read(char *buffer,
              char **buffer_location,
              off_t offset, int buffer_length, int *eof, void *data)
{
    int len;                /* The number of bytes actually used */

    /*
     * It's static so it will still be in memory
     * when we leave this function
     */
    static char my_buffer[80];

    /*
     * We give all of our information in one go, so if anybody asks us
     * if we have more information the answer should always be no.
     */
    if (offset > 0)
        return 0;

    /*
     * Fill the buffer and get its length
     */
    len = sprintf(my_buffer, "Timer called %d times so far\n", TimerIntrpt);

    /*
     * Tell the function which called us where the buffer is
     */
    *buffer_location = my_buffer;

    /*
     * Return the length
     */
    return len;
}

/*
 * Initialize the module - register the proc file
 */
int __init init_module()
{
    /*
     * Create our /proc file
     */
    Our_Proc_File = proc_create(PROC_ENTRY_FILENAME, 0644, NULL, NULL);

    if (Our_Proc_File == NULL) {
        remove_proc_entry(PROC_ENTRY_FILENAME, NULL);
        printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
               PROC_ENTRY_FILENAME);
        return -ENOMEM;
    }
    proc_set_size(Our_Proc_File, 80);
    proc_set_user(Our_Proc_File,  GLOBAL_ROOT_UID, GLOBAL_ROOT_GID);

    /*
     * Put the task in the work_timer task queue, so it will be executed at
     * next timer interrupt
     */
    my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
    queue_delayed_work(my_workqueue, &Task, 100);

    printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_FILENAME);

    return 0;
}

/*
 * Cleanup
 */
void __exit cleanup_module()
{
    /*
     * Unregister our /proc file
     */
    remove_proc_entry(PROC_ENTRY_FILENAME, NULL);
    printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME);

    die = 1;                /* keep intrp_routine from queueing itself */
    cancel_delayed_work(&Task);     /* no "new ones" */
    flush_workqueue(my_workqueue);  /* wait till all "old ones" finished */
    destroy_workqueue(my_workqueue);

    /*
     * Sleep until intrpt_routine is called one last time. This is
     * necessary, because otherwise we'll deallocate the memory holding
     * intrpt_routine and Task while work_timer still references them.
     * Notice that here we don't allow signals to interrupt us.
     *
     * Since WaitQ is now not NULL, this automatically tells the interrupt
     * routine it's time to die.
     */
}
