#include <linux/config.h>
#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
#define MODVERSIONS
#endif
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/ctype.h> //for numeric conversion
#include <stdarg.h>

#include <cbs_proc_fs.h>
#include <cbs.h>
#include <time.h>


#include <asm/uaccess.h>  /* for get_user and put_user */


#define MESSAGE_LENGTH 80

extern struct cbs_struct * exec;
extern struct proc_dir_entry proc_root;
struct proc_dir_entry *cbs_proc_root = NULL;

extern spinlock_t generic_scheduler_lock __cacheline_aligned;

static int cbs_read_sched(char *page, char **start, off_t off, int count,
                           int *eof, void *data)
{
        PROC_PRINT_VARS;
        struct task_struct *t;
	int i, done=0;
	int is_cbs=0;
	struct cbs_struct* cbs_param;
	unsigned long flags;
	unsigned long int period,max_budg;
	long long int current_budg;
	unsigned long long int deadline;
	unsigned long long int time_read,tmp;
	tmp = sched_read_clock();
	if (tmp != 0)
	{
		time_read = clock2us(tmp);
		PROC_PRINT(" time: %lli\n",time_read);
	} else 	
	{
		PROC_PRINT("error: time =0\n");
		goto end;
	}
	i=0;
	PROC_PRINT("   pid|cbs|priority|policy|state|    period|max_budget|cur_budget|            deadline|                diff\n\n");
	//Warning: this spinlock introduce an high latency !!!
	spin_lock_irqsave(&generic_scheduler_lock,flags);
	for_each_task(t) {
		if(t->private_data!=NULL)
		{
			cbs_param = (struct cbs_struct*)(t->private_data);
			is_cbs = 1;
			period = cbs_param->period;
			max_budg = cbs_param->max_budget;
			current_budg = cbs_param->c;
			deadline= cbs_param->deadline;
		} else {
			is_cbs = 0;
			period = 0;
			max_budg = 0;
			current_budg = 0;
			deadline=0;
		}
		PROC_PRINT("%6d %3i %8ld %6ld %5ld %10lu %10lu %10lu %20lu %20li\n", 
			t->pid,
			is_cbs,
			t->rt_priority, 
			t->policy, 
			t->state, 
			period,
			max_budg,
			clock2us(current_budg),
			clock2us(deadline),
			(long int)(clock2us(deadline) - time_read));
	}
	spin_unlock_irqrestore(&generic_scheduler_lock,flags);
	done = 1;
	PROC_PRINT("\n");
end:
	PROC_PRINT_DONE;
	if(!done)
		spin_unlock_irqrestore(&generic_scheduler_lock,flags);
}  /* End function - cbs_read_sched */

static int cbs_read_modinfo(char *page, char **start, off_t off, int count,
                           int *eof, void *data)
{
        PROC_PRINT_VARS;
        PROC_PRINT("\nCBS generic scheduler.\n\n");
	PROC_PRINT("multitasking: ");
#ifdef __MULTITASK__
        PROC_PRINT("on\n");
#else
        PROC_PRINT("off\n");
#endif  
#ifdef __GRUB__
	PROC_PRINT("grub: on \n");
#ifdef __GRUB_PWR__
	PROC_PRINT("\tgrub power: on\n");
#endif	
	PROC_PRINT("\tgrub extra bandwidth: ")
#ifdef __GRUB_EXTRA_BANDWIDTH__
	PROC_PRINT("\ton");
#else
	PROC_PRINT("\toff");
#endif	
#else
	PROC_PRINT("cbs: on \n");
#endif	
	PROC_PRINT("precise allocation: ");
#ifdef __SLEEP__
	PROC_PRINT("on\n");
#else
	PROC_PRINT("off\n");
#endif	
	PROC_PRINT("high resoluton timer: ");
#ifdef __HRT__
	PROC_PRINT("on\n");
#else
	PROC_PRINT("off\n");
#endif	
        PROC_PRINT("\n");

        PROC_PRINT_DONE;

}  /* End function - cbs_read_param */

static ssize_t cbs_write_param(struct file *file, const char *buf, unsigned long length, void *offset)
{
    int i, n, rw;
    int pid;
    unsigned long flags;
    unsigned long num ;
    char command[MESSAGE_LENGTH]; 
    struct task_struct *tsk;
    //MOD_INC_USE_COUNT;
    for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)
        get_user(command[i], buf+i);
 
    command[i] = '\0';  /* terminated string */
    n = 0;
    /* looking for read/write */
    printk("command: %s\n\n",command);
    while ((command[n] != 'r') && (command[n] != 'w') && (n < i)) { ++n; };
    if(command[n] == 'r')
        rw = 0;
    else if (command[n] == 'w')
        rw = 1;
    else 
    {
	printk("Error (begin): n = %i",n);
	goto error0;
    }
    n++;
    /* looking for pid */
    while ((command[n] != 't') && (n < i)){ ++n;};
    if(command[n] != 't')
	{
		printk("Error : n = %i",n);
		goto error0;
	}
    n++;
    if (!isdigit(command[n]))
	{
		printk("Error : n = %i",n);
		goto error0;
	}
    num = 0;
    while(isdigit(command[n]))
    {
		num = num*10 + command[n] - '0';
		n++;
	}
	pid = num;
	printk("\n pid = %i\n",pid);
    ++n;
 
    tsk = find_task_by_pid(pid);
    if(tsk != NULL)
    {
        if (rw == 0) 
        {
            spin_lock_irqsave(&generic_scheduler_lock,flags);
            if(tsk != NULL) 
            {	
                if(tsk->private_data != NULL)
                {
                    struct cbs_struct * cbs = (struct cbs_struct*)(tsk->private_data);
                    unsigned long int max_budget = cbs->max_budget, period = cbs->period;
                    long long int curr_budget = cbs->c;
                    unsigned long int deadline = cbs->deadline;
                    long int rt_priority = tsk->rt_priority, policy = tsk->policy, state = tsk->state;
                    unsigned long int time_read;
                    spin_unlock_irqrestore(&generic_scheduler_lock,flags);

                    time_read =clock2us(sched_read_clock());
                    deadline = clock2us(deadline);
                    printk("\n time: %20lu\n rt_priority: %20ld\n policy: %20ld\n state: \t%ld\n period: \t%lu\n max_budget: \t%lu\n current budget: \t%lu\n deadline: \t%lu\n diff: \t%li\n", 
                           time_read, rt_priority, policy, state, period,
                           max_budget, clock2us(curr_budget), deadline,
                           (long int)(deadline - time_read));
                    return i;
                }
                spin_unlock_irqrestore(&generic_scheduler_lock,flags);
                printk("task %i is not a cbs task",pid);
                goto error1;
            }
            spin_unlock_irqrestore(&generic_scheduler_lock,flags);
        }
        else 
        {
            unsigned long int period, budget;
            unsigned long int period_clock, budget_clock;
            /* looking for budget */
            while ((command[n] != 'b') && (n < i)){ ++n;};
            if(command[n] != 'b')
            {
                printk("Error : n = %i",n);
                goto error0;
            }
            n++;
            if (!isdigit(command[n]))
            {
                printk("Error : n = %i",n);
                goto error0;
            }
            num = 0;
            while(isdigit(command[n]))
            {
                num = num*10 + command[n] - '0';
                n++;
            }
            budget = num;
            printk("budget = %lu\n",budget);
            n++;
            /* looking for period */
            while (!(isalpha(command[n])) && (n < i)){ ++n;};
            if (command[n] != 'p')
            {
                printk("Error : n = %i",n);
                goto error0;
            }
            n++;
            if (!isdigit(command[n]))
            {
                printk("Error : n = %i",n);
                goto error0;
            }
            num = 0;
            while (isdigit(command[n]))
            {
                num = num*10 + command[n] - '0';
                n++;
            }	
            period = num;
            printk("period = %lu\n\n",period);
            if ((budget < 10000) || (budget > 10000000))
            {
                printk("Error : budget must be > 10000 or < 10000000\n");
                goto error1;
            }
            if ((period < 10000) || (period > 10000000))
            {
                printk("Error : period must be > 10000 or < 10000000\n");
                goto error1;
            }
            if (period < budget)
            {
                printk("Error : period must be greater than budget\n");
                goto error1;
            }
            period_clock = us2clock(period);
            budget_clock = us2clock(budget);
            spin_lock_irqsave(&generic_scheduler_lock,flags);
            if(tsk != NULL) 
            {	
                if(tsk->private_data != NULL)
                {
                    struct cbs_struct * cbs = (struct cbs_struct*)(tsk->private_data);
                    cbs->period = period;
                    cbs->period_clock = period_clock;
                    cbs->max_budget = budget;
                    cbs->max_budget_clock = budget_clock;
                    spin_unlock_irqrestore(&generic_scheduler_lock,flags);
                    printk("task %i updated: max_budget = %lu, period = %lu\n"
                           ,pid, cbs->max_budget, cbs->period);
                    //MOD_DEC_USE_COUNT;
                    return i;
                }
                spin_unlock_irqrestore(&generic_scheduler_lock,flags);
                printk("task %i is not a cbs task",pid);
                goto error1;
            }
            spin_unlock_irqrestore(&generic_scheduler_lock,flags);
        }
    }
    
	printk("pid %i is not a valid pid",pid);
	goto error1;
  error0:
	printk("\nError! Command format: t<task\'s pid> b<budget> p<period>\n");
	printk("Example: echo t3214 b10000 p100000 > /proc/cbs/param\n");
	//MOD_DEC_USE_COUNT;
	return -1;
  error1: 
	//MOD_DEC_USE_COUNT;
	return -1;
			
}

int cbs_proc_register(void)
{
        struct proc_dir_entry *proc_sched_ent;
	struct proc_dir_entry *proc_param_ent;
	struct proc_dir_entry *proc_modinfo_ent;

        cbs_proc_root = create_proc_entry("cbs", S_IFDIR, 0);
        if (!cbs_proc_root) {
                printk("Unable to initialize /proc/cbs\n");
                return(-1);
        }
        cbs_proc_root->owner = THIS_MODULE;
        proc_sched_ent = create_proc_entry("scheduler", S_IFREG|S_IRUGO|S_IWUSR, cbs_proc_root);
        if (!proc_sched_ent) {
                printk("Unable to initialize /proc/cbs/scheduler\n");
                return(-1);
        }
        proc_sched_ent->read_proc = cbs_read_sched;
        
	proc_modinfo_ent = create_proc_entry("modinfo", S_IFREG|S_IRUGO|S_IWUSR, cbs_proc_root);
        if (!proc_modinfo_ent) {
                printk("Unable to initialize /proc/cbs/modinfo\n");
                return(-1);
        }
        proc_modinfo_ent->read_proc = cbs_read_modinfo;
	
        proc_param_ent = create_proc_entry("param", S_IFREG|S_IRUGO|S_IWUSR, cbs_proc_root);
        if (!proc_param_ent) {
                printk("Unable to initialize /proc/cbs/param\n");
                return(-1);
        }
        proc_param_ent->write_proc = cbs_write_param;
	
        return(0);
}  /* End function - cbs_proc_sched_register */


void cbs_proc_unregister(void)
{

        remove_proc_entry("scheduler", cbs_proc_root);
        remove_proc_entry("param", cbs_proc_root);
        remove_proc_entry("cbs", 0);
}  /* End function - cbs_proc_sched_unregister */

