/* 
 * Real-Time and Multimedia Systems Laboratory
 * Copyright (c) 2000 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Real-Time and Multimedia Systems Laboratory
 *  Attn: Prof. Raj Rajkumar
 *  Electrical and Computer Engineering, and Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 *  or via email to raj@ece.cmu.edu
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * Portable QoS manager, implementing the comcepts described in
 * ``Adaptive Bandwidth Reservation for Multimedia Computing''
 * by L. Abeni and G. Buttazzo
 * IEEE Real Time Computing Systems and Applications 1999
 *
 *			program developed by Luca Abeni
 *					luca@sssup.it
 *					http://hartik.sssup.it/~luca
 */

/************************************************************************/
/*	The ``body'' of the portable QoS manager			*/
/*	OS independent code						*/
/************************************************************************/
#include <stdio.h>

#include <arch/local.h>

#include "prop.h"
#include "ps.h"
#include "req.h"
#include "simpleadj.h"
#include "qos.h"
#include "stats.h"
#include "ll.h"

#define SINGLE

/*
#define QOSDBG
*/

struct qtask_global *qosq;	/* task set */
struct qoscmd {
    BYTE type;
    ID task;
    float newvalue;
    float secval;
    float thirdval;
    DWORD newvalue1;
    DWORD secval1;
    DWORD thirdval1;
};

#define P_CHANGE 0
#define Q_CHANGE 1
#define GLOB_QCHANGE 2
#define B_CHANGE 3
#define QTASK_REGISTER 4
#define PSTASK_REGISTER 5
#define B_GET 6
#define PSTASK_UNREGISTER 7
#define QTASK_UNREGISTER 8

/* For communication between adaptive tasks and the global QoS adapter... */
PORT qosport = 0;

/* Amount of CPU bandwidth reserved for PPS tasks */
static float ppsbw = 0;

/* PPS task scheduling quantum */
static DWORD ppsq = 0;

static float rmu = 0;

void (*req_init) (void) = simpleadj_init;
void *(*req_create) (DWORD q, DWORD desired, float maxfb, DWORD period) =
    simpleadj_create;
void (*req_destroy) (void *parms) = simpleadj_destroy;
void (*req_setb) (void *parms, float band) = simpleadj_setb;
float (*req_feedb) (void *parms, int l) = simpleadj_feedb;

void (*rescale_init) (float fbw) = prop_init;
void *(*rescale_create) (float u, float w) = prop_create;
void (*rescale_destroy) (void *p) = prop_destroy;
float (*rescale_getband) (void *p) = prop_getband;
void (*rescale_setband) (void *p, float u) = prop_setband;
void (*rescale_compress) (void) = prop_compress;

#ifdef __ADJ_STATS__
/* Statistics... */
struct t1stat adjstats[10000][10];
#endif

/* This is the function to plug a different feedback function in */
void setreq(struct reqactions *p)
{
    req_init = p->req_init;
    req_create = p->req_create;
    req_destroy = p->req_destroy;
    req_setb = p->req_setb;
    req_feedb = p->req_feedb;
}

/*
 * Recomputes the (Q,T) scheduling parameters each time that an adaptation
 * action runs...
 */
void adjustparms(void)
{
    struct qtask_global *p;

    p = qosq;
    while (p != NULL) {
	if (p->type == FIXEDP) {
	    p->oldq = p->q;
#ifdef QOSDBG
	    cprintf("Task %p: Changin' Q from %lu", p, p->q);
#endif
	    p->q = BAND2CAPACITY(p->drel, rescale_getband(p->compress));

#ifdef QOSDBG
	    cprintf(" (cpr BW: %f)", rescale_getband(p->compress));
	    cprintf(" to %lu\n", p->q);
#endif
#if 0
	    if (p->q != p->oldq) {
#else
	    if ((p->q > p->oldq + 1000) || (p->q < p->oldq - 100)) {
#endif
		p->valid = Q_CHANGED;
	    } else {
		p->q = p->oldq;
	    }
	}
#if 0				/* I am not currently supporting period adaptation... (sorry!) */
	if (p->type == FIXEDQ) {
	    p->oldp = p->per;
	    p->per = (p->q / rescale_getband(p->compress));
	    p->valid = PERIOD_CHANGED;
	}
#endif
	p = p->next;
    }
}

/************************************************************************/
/*		Manager task: receives and execute all Qos CMD		*/
/************************************************************************/
TASK manager(void)
{
    PORT cmdport;
    struct qoscmd cmd;
    int res;
    float band, oldband;
    struct qtask_global *task;
    WORD period;
    struct qtask_global *p;

    /* Inits all the global structures... */
    pscompr_init(ppsbw);
    band = 1.0 - (sys_utilization(rmu) / 100000.0);
#ifdef QOSDBG
    cprintf("Free BW for QMan: %f\n", band);
    cprintf("%f to MM tasks\n", band - 0.01 - ppsbw);
#endif
    cprintf("Free BW for QMan: %f\n", band);
    cprintf("%f to MM tasks\n", band - 0.01 - ppsbw);
    if (band - 0.01 - ppsbw < 0) {
	cprintf("QoS panic: no enough free bandwidth...\n");
	qos_abort(99);
    }
    rescale_init(band - 0.01 - ppsbw);

    /*... And creates a QoS port for communicating with adaptive tasks!!! */
    cmdport = makeport("QoS_Port", sizeof(struct qoscmd), O_RDONLY);
    if (cmdport == INVALID_PORT) {
	qos_abort(99);
    }

    /* This is the main cycle */
    for (;;) {
	/*Somewhat activated the task: there should be a command to exec... */
	res = receivefromport(cmdport, &cmd);
	if (res > 0) {
#ifdef QOSDBG
	    cprintf("QoS Manager activate --> Command code: %d\n",
		    cmd.type);
#endif

	    /* Obtain the ID of the task requiring the command */
	    task = id2num(qosq, cmd.task);

	    /* The only two commands that does not require a task ID */
	    /* are the register commands */
	    if ((task == NULL) &&
		(cmd.type != QTASK_REGISTER) &&
		(cmd.type != PSTASK_REGISTER)) {
		cprintf("ARRRRG %d\n", cmd.type);
		qos_abort(500);
	    }
	    switch (cmd.type) {
	    case P_CHANGE:
		/* A change in the task period is required */
		task->oldp = task->per;
		task->per = cmd.newvalue;
		if (task->per == 0) {
		    cprintf("Attempting to set period to 0...\n");
		    qos_abort(10000 + cmd.newvalue);
		}
		task->valid = PERIOD_CHANGED;
		lowlev_change(qosq);
		task->drel = task_getabsdl(task);
		break;

	    case B_CHANGE:
		/* A change in the task reserved bandwidth is required */
		band = (float) cmd.newvalue;
#ifdef QOSDBG
		cprintf("Newband: %f\n", band);
#endif
		/* Is the required variation negligible? */
		oldband = rescale_getband(task->compress);
		if ((oldband > band + 0.1) || (oldband < band - 0.1)) {
		    /* No, it is significant: Set the new required Bw... */
		    rescale_setband(task->compress, band);
		    /* ... Perform the compression... */
		    rescale_compress();
		    /* ... And adjust the scheduling parameters */
		    adjustparms();
		    lowlev_change(qosq);
		}
		break;

	    case Q_CHANGE:
		/* A change in the task reserved computation time is required */
		band = USEC2BAND(cmd.newvalue, task->per);
#ifdef QOSDBG
		cprintf("Newband: %f\n", band);
#endif
		rescale_setband(task->compress, band);
		rescale_compress();
		adjustparms();
		lowlev_change(qosq);
		break;

	    case GLOB_QCHANGE:
		/* Global Bandwidth change... */
		task->reqb = cmd.newvalue;
		p = qosq;
		while (p != NULL) {
		    if (p->reqb) {
			band = p->reqb;
			rescale_setband(p->compress, band);
		    }
		    p = p->next;
		}
		rescale_compress();
		adjustparms();
		lowlev_change(qosq);
		break;

	    case QTASK_REGISTER:
		/* Register a new MM or ED task */
		task = manager_registertask();
		task->next = qosq;
		qosq = task;

		task->id = cmd.task;
		task->compress = rescale_create(cmd.newvalue, cmd.secval);
		task->per = cmd.newvalue1;
		task->drel = cmd.secval1;
		task->q = cmd.thirdval;

		task->valid = VALID;
		task->type = FIXEDP;
		break;

	    case PSTASK_REGISTER:
		/* Register a new PPS task */
		task = manager_registertask();
		task->next = qosq;
		qosq = task;
		task->id = cmd.task;
		task->compress = pscompr_create(cmd.newvalue);
		task->q = cmd.newvalue1;
		band = pscompr_getband(task->compress);
		period = BAND2PERIOD(task->q, band);
		task->per = 1000000;

		task->valid = VALID;
		task->type = PPSTASK;

		p = qosq;
		while (p != NULL) {
		    if (p->type == PPSTASK) {
			band = pscompr_getband(p->compress);
			period = BAND2PERIOD(p->q, band);

			/*          p_change(i, period);  */
			p->oldp = p->per;
			p->per = period;
			if (p->per == 0)
			    qos_abort(20000 + period);
			p->valid = PERIOD_CHANGED;
			p->drel = task_getabsdl(p);
#ifdef QOSDBG
			cprintf
			    ("PseudoPS Task %p: changin' period to %lu\n",
			     p, period);
#endif
		    }
		    p = p->next;
		}
		lowlev_change(qosq);
		break;

	    case PSTASK_UNREGISTER:
		/* Unregister a PPS task */
		pscompr_destroy(task->compress);
		if (qosq == task) {
		    qosq = task->next;
		}
		p = qosq;
		while (p != NULL) {
		    if (p->next == task) {
			p->next = task->next;
		    }
		    if (p->type == PPSTASK) {
			band = pscompr_getband(p->compress);
			period = BAND2PERIOD(p->q, band);

			/*          p_change(i, period);  */
			p->oldp = p->per;
			p->per = period;
			if (p->per == 0)
			    qos_abort(20000 + period);
			p->valid = PERIOD_CHANGED;
			p->drel = task_getabsdl(p);
#ifdef QOSDBG
			cprintf
			    ("PseudoPS Task %p: changin' period to %lu\n",
			     p, period);
#endif
		    }
		    p = p->next;
		}
		lowlev_change(qosq);
		manager_unregistertask(task);
		break;

	    case QTASK_UNREGISTER:
		/* Unregister a PPS task */
		rescale_destroy(task->compress);
		if (qosq == task) {
		    qosq = task->next;
		}
		p = qosq;
		while (p != NULL) {
		    if (p->next == task) {
			p->next = task->next;
		    }
		    p = p->next;
		}
		manager_unregistertask(task);
		break;
	    case B_GET:
		cmd.newvalue = rescale_getband(task->compress);
		break;
	    default:
		qos_abort(127);
	    }
	} else {
	    if (res < 0) {
		cprintf("QoS manager: problem receiving...");
		perror("");
		qos_abort(100);
	    }
	    cprintf("Someone did not call me... Code: %d\n", cmd.type);
	    qos_abort(96);
	}
    }
}

/*
 * The following are the QoSLib stubs that send requests
 * to the QoS Manager Task
 */
void globalfeedbk(ID task, float newb)
{
    struct qoscmd cmd;
    cmd.task = task;
    cmd.newvalue = newb;
    cmd.type = GLOB_QCHANGE;

    sendtoport(qosport, &cmd);
}

void p_change(ID task, WORD newp)
{
    struct qoscmd cmd;

    cmd.type = P_CHANGE;
    cmd.task = task;
    cmd.newvalue = newp;

    sendtoport(qosport, &cmd);
}

void qtask_register(ID task, float b, int w, DWORD p, DWORD dl, DWORD q)
{
    struct qoscmd cmd;

    cmd.type = QTASK_REGISTER;
    cmd.task = task;
    cmd.newvalue = b;
    cmd.secval = w;
    cmd.newvalue1 = p;
    cmd.secval1 = dl;
    cmd.thirdval = q;

    sendtoport(qosport, &cmd);
}

void pstask_register(ID task, float w, WORD q)
{
    struct qoscmd cmd;

    cmd.type = PSTASK_REGISTER;
    cmd.task = task;
    cmd.newvalue = w;
    cmd.newvalue1 = q;

    sendtoport(qosport, &cmd);
}

void pstask_unregister(ID task)
{
    struct qoscmd cmd;

    cmd.type = PSTASK_UNREGISTER;
    cmd.task = task;

    sendtoport(qosport, &cmd);
}

void qtask_unregister(ID task)
{
    struct qoscmd cmd;

    cmd.type = QTASK_UNREGISTER;
    cmd.task = task;

    sendtoport(qosport, &cmd);
}

void q_change(ID task, WORD newq)
{
    struct qoscmd cmd;

    cmd.type = Q_CHANGE;
    cmd.task = task;
    cmd.newvalue = newq;

    sendtoport(qosport, &cmd);
}

void b_change(ID task, float newb)
{
    struct qoscmd cmd;

    cmd.type = B_CHANGE;
    cmd.task = task;
    cmd.newvalue = newb;

    sendtoport(qosport, &cmd);
}

/*
 * Initializes the QoS Manager
 */
int qman_init(struct qmparm *p)
{

    qosq = NULL;

    if (p == NULL) {
	ppsbw = PSU;
	ppsq = PSQ;
	rmu = 0.69;
    } else {
	ppsbw = p->psu;
	ppsq = p->psq;
	rmu = p->rmu;
    }

    if (server_create() < 0) {
	return -1;
    }

    /* Usefull only for multithreaded environments, like HARTIK */
    req_init();

    qosport = connecttoport("QoS_Port", sizeof(struct qoscmd), O_WRONLY);

    return 1;
}

/*
 * Creates a QoS Managed Task
 */
ID qset_addtask(struct qparm * t)
{
    float band;
    ID res;
    DWORD drel;
    struct qtask_local *newtask;

    newtask = getfreedescriptor();
    band = USEC2BAND(t->wcet, t->per);
#ifdef QOSDBG
    cprintf("%d / %d --- ", t->wcet, t->per);
    cprintf("Creating new QoS task with band %f", band);
#endif
    if (t->reduce != 0) {
	drel = t->per / t->reduce;
    } else {
	drel = t->per;
    }
    if (drel == 0) {
	cprintf("DREL 0???\n");
	cprintf("Period: %lu reduce: %d\n", t->per, t->reduce);
	qos_abort(111);
    }
#ifdef QOSDBG
    cprintf(" --- deadline %d", drel);
#endif
    /*      initial Q, desired late value...        */
    newtask->adj = req_create(t->wcet, 0, t->per, t->per);
    res =
	inittask(t->name, t->body, t->arg, t->w, t->wcet, t->per, drel,
		 t->model);

    if (res == NULLID) {
	return NULLID;
    }
    newtask->id = res;

#ifdef QOSDBG
    cprintf(" --- Activated!!!\n");
#endif

    return res;			/* The task ID... */
}

void qtask_changeperiod(WORD newp)
{
    ID p;

    p = currenttask();

    p_change(p, newp);
}

void qtask_end(int retvalue)
{
    ID p;

    p = currenttask();

/* Something like task_unregister... */
#if 0				/* TODO!!!! */
    set[i].valid = UNVALID;
    req_destroy(set[i].adj);
    rescale_destroy(set[i].compress);
    endtask();
#endif
}

/*
 * End of a MM task period:
 *	- computes the scheduling error
 *	- apply the feedback function
 *	- send a request to the QoS Manager task
 *	- go to sleep :)
 */
DWORD qtask_endcycle(void)
{
    int l;
    struct qtask_local *myself;
    ID myid;
    float oldb, newb;

    myself = getdescr();
    myid = currenttask();
    oldb = glob_getband(myid);

    /* First of all, set the compressed bandwidth */
#ifdef QOSDBG
    cprintf("Setting band to %f...\n", oldb);
#endif
    req_setb(myself->adj, oldb);

#ifdef SINGLE
    l = howlate(myid);
#ifdef QOSDBG
    cprintf("Howlate: %d\n", l);
#endif
    newb = req_feedb(myself->adj, l);
#ifdef QOSDBG
    cprintf("NewBW: %f\n", newb);
#endif
    b_change(myid, newb);
#else
    l = howlate(myid);
    newb = req_feedb(myself->adj, l);

    globalfeedbk(myid, newb);
#endif

    resetcount();

    endinstance();

    return l;
}

/*
 * Creates a new PPS task
 */
ID ps_addtask(struct psparm * t)
{
    ID res;
    WORD q;
    struct qtask_local *newtask;

    newtask = getfreedescriptor();

#ifdef QOSDBG
    cprintf("Parameters: Q=%ld, W=%f", t->quantum, t->w);
#endif

    if (t->quantum != 0) {
	q = t->quantum;
    } else {
	q = ppsq;
    }
    res = initpstask(t->name, t->body, t->arg, t->w, q);
    if (res == NULLID) {
	perror("PseudoPS Task Create Error:");
	qos_abort(1002);
    }
    newtask->id = res;
#ifdef QOSDBG
    cprintf("Created!!!\n");
#endif

    return res;
}
