/*	$NetBSD: altq_jobs.c,v 1.14 2025/01/08 13:00:04 joe Exp $	*/
/*	$KAME: altq_jobs.c,v 1.11 2005/04/13 03:44:25 suz Exp $	*/
/*
 * Copyright (c) 2001, the Rector and Board of Visitors of the
 * University of Virginia.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer.
 *
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * Neither the name of the University of Virginia nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
/*                                                                     
 * JoBS - altq prototype implementation                                
 *                                                                     
 * Author: Nicolas Christin <nicolas@cs.virginia.edu>
 *
 * JoBS algorithms originally devised and proposed by		       
 * Nicolas Christin and Jorg Liebeherr.
 * Grateful acknowledgments to Tarek Abdelzaher for his help and       
 * comments, and to Kenjiro Cho for some helpful advice.
 * Contributed by the Multimedia Networks Group at the University
 * of Virginia. 
 *
 * Papers and additional info can be found at 
 * http://qosbox.cs.virginia.edu
 *                                                                      
 */ 							               

/*
 * JoBS queue
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: altq_jobs.c,v 1.14 2025/01/08 13:00:04 joe Exp $");

#ifdef _KERNEL_OPT
#include "opt_altq.h"
#include "opt_inet.h"
#endif

#ifdef ALTQ_JOBS  /* jobs is enabled by ALTQ_JOBS option in opt_altq.h */

#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/kauth.h>

#ifdef __FreeBSD__
#include <sys/limits.h>
#endif

#include <net/if.h>
#include <net/if_types.h>

#include <altq/altq.h>
#include <altq/altq_conf.h>
#include <altq/altq_jobs.h>

#ifdef ALTQ3_COMPAT
/*
 * function prototypes
 */
static struct jobs_if *jobs_attach(struct ifaltq *, u_int, u_int, u_int);
static void jobs_detach(struct jobs_if *);
static int jobs_clear_interface(struct jobs_if *);
static int jobs_request(struct ifaltq *, int, void *);
static void jobs_purge(struct jobs_if *);
static struct jobs_class *jobs_class_create(struct jobs_if *,
    int, int64_t, int64_t, int64_t, int64_t, int64_t, int);
static int jobs_class_destroy(struct jobs_class *);
static int jobs_enqueue(struct ifaltq *, struct mbuf *);
static struct mbuf *jobs_dequeue(struct ifaltq *, int);

static int jobs_addq(struct jobs_class *, struct mbuf *, struct jobs_if*);
static struct mbuf *jobs_getq(struct jobs_class *);
static struct mbuf *jobs_pollq(struct jobs_class *);
static void jobs_purgeq(struct jobs_class *);

static int jobscmd_if_attach(struct jobs_attach *);
static int jobscmd_if_detach(struct jobs_interface *);
static int jobscmd_add_class(struct jobs_add_class *);
static int jobscmd_delete_class(struct jobs_delete_class *);
static int jobscmd_modify_class(struct jobs_modify_class *);
static int jobscmd_add_filter(struct jobs_add_filter *);
static int jobscmd_delete_filter(struct jobs_delete_filter *);
static int jobscmd_class_stats(struct jobs_class_stats *);
static void get_class_stats(struct class_stats *, struct jobs_class *);
static struct jobs_class *clh_to_clp(struct jobs_if *, u_long);
static u_long clp_to_clh(struct jobs_class *);

static TSLIST *tslist_alloc(void);
static void tslist_destroy(struct jobs_class *);
static int tslist_enqueue(struct jobs_class *, u_int64_t);
static void tslist_dequeue(struct jobs_class *);
static void tslist_drop(struct jobs_class *);

static int enforce_wc(struct jobs_if *);
static int64_t* adjust_rates_rdc(struct jobs_if *);
static int64_t* assign_rate_drops_adc(struct jobs_if *);
static int64_t* update_error(struct jobs_if *);
static int min_rates_adc(struct jobs_if *);
static int64_t proj_delay(struct jobs_if *, int);
static int pick_dropped_rlc(struct jobs_if *);

altqdev_decl(jobs);

/* jif_list keeps all jobs_if's allocated. */
static struct jobs_if *jif_list = NULL;

typedef unsigned long long ull;

/* setup functions */

static struct jobs_if *
jobs_attach(struct ifaltq *ifq, u_int bandwidth, u_int qlimit, u_int separate)
{
	struct jobs_if *jif;

	jif = malloc(sizeof(struct jobs_if), M_DEVBUF, M_WAITOK|M_ZERO);
	if (jif == NULL)
	        return NULL;

	jif->jif_bandwidth = bandwidth;
	jif->jif_qlimit = qlimit;
	jif->jif_separate = separate;
#ifdef ALTQ_DEBUG
	printf("JoBS bandwidth = %d bps\n", (int)bandwidth);
	printf("JoBS buffer size = %d pkts [%s]\n",
	       (int)qlimit, separate?"separate buffers":"shared buffer");
#endif
	jif->jif_maxpri = -1;
	jif->jif_ifq = ifq;

	jif->wc_cycles_enqueue = 0;
	jif->avg_cycles_enqueue = 0;
	jif->avg_cycles2_enqueue = 0;
	jif->bc_cycles_enqueue = ALTQ_INFINITY;
	jif->wc_cycles_dequeue = 0;
	jif->avg_cycles_dequeue = 0;
	jif->avg_cycles2_dequeue = 0;
	jif->bc_cycles_dequeue = ALTQ_INFINITY;
	jif->total_enqueued = 0;
	jif->total_dequeued = 0;

	/* add this state to the jobs list */
	jif->jif_next = jif_list;
	jif_list = jif;

	return jif;
}

static void
jobs_detach(struct jobs_if *jif)
{
	(void)jobs_clear_interface(jif);

	/* remove this interface from the jif list */
	if (jif_list == jif)
		jif_list = jif->jif_next;
	else {
		struct jobs_if *p;

		for (p = jif_list; p != NULL; p = p->jif_next)
			if (p->jif_next == jif) {
				p->jif_next = jif->jif_next;
				break;
			}
		ASSERT(p != NULL);
	}
	free(jif, M_DEVBUF);
}

/*
 * bring the interface back to the initial state by discarding
 * all the filters and classes.
 */
static int
jobs_clear_interface(struct jobs_if *jif)
{
	struct jobs_class	*cl;
	int	pri;

	/* free the filters for this interface */
	acc_discard_filters(&jif->jif_classifier, NULL, 1);

	/* clear out the classes */
	for (pri = 0; pri <= jif->jif_maxpri; pri++)
		if ((cl = jif->jif_classes[pri]) != NULL)
			jobs_class_destroy(cl);

	return 0;
}

static int
jobs_request(struct ifaltq *ifq, int req, void *arg)
{
	struct jobs_if	*jif = (struct jobs_if *)ifq->altq_disc;

	switch (req) {
	case ALTRQ_PURGE:
		jobs_purge(jif);
		break;
	}
	return 0;
}

/* discard all the queued packets on the interface */
static void
jobs_purge(struct jobs_if *jif)
{
	struct jobs_class *cl;
	int pri;

	for (pri = 0; pri <= jif->jif_maxpri; pri++) {
		if ((cl = jif->jif_classes[pri]) != NULL && !qempty(cl->cl_q))
			jobs_purgeq(cl);
	}
	if (ALTQ_IS_ENABLED(jif->jif_ifq))
		jif->jif_ifq->ifq_len = 0;
}

static struct jobs_class *
jobs_class_create(struct jobs_if *jif, int pri, int64_t adc, int64_t rdc,
    int64_t alc, int64_t rlc, int64_t arc, int flags)
{
	struct jobs_class *cl, *scan1, *scan2;
	int s;
	int class_exists1, class_exists2;
	int i, j;
	int64_t tmp[JOBS_MAXPRI];
	u_int64_t now;

	if ((cl = jif->jif_classes[pri]) != NULL) {
		/* modify the class instead of creating a new one */
		s = splnet();
		if (!qempty(cl->cl_q))
			jobs_purgeq(cl);
		splx(s);
	} else {
		cl = malloc(sizeof(struct jobs_class), M_DEVBUF,
		    M_WAITOK|M_ZERO);
		if (cl == NULL)
			return NULL;

		cl->cl_q = malloc(sizeof(class_queue_t), M_DEVBUF,
		    M_WAITOK|M_ZERO);
		if (cl->cl_q == NULL)
			goto err_ret;

		cl->arv_tm = tslist_alloc();
		if (cl->arv_tm == NULL)
			goto err_ret;
	}

	jif->jif_classes[pri] = cl;

	if (flags & JOCF_DEFAULTCLASS)
		jif->jif_default = cl;

	qtype(cl->cl_q) = Q_DROPTAIL;
	qlen(cl->cl_q) = 0;
	cl->service_rate = 0;
	cl->min_rate_adc = 0;
	cl->current_loss = 0;
	cl->cl_period = 0;
	PKTCNTR_RESET(&cl->cl_arrival);
	PKTCNTR_RESET(&cl->cl_rin);
	PKTCNTR_RESET(&cl->cl_rout);
	PKTCNTR_RESET(&cl->cl_rout_th);
	PKTCNTR_RESET(&cl->cl_dropcnt);
	PKTCNTR_RESET(&cl->st_arrival);
	PKTCNTR_RESET(&cl->st_rin);
	PKTCNTR_RESET(&cl->st_rout);
	PKTCNTR_RESET(&cl->st_dropcnt);
	cl->st_service_rate = 0;
	cl->cl_lastdel = 0;
	cl->cl_avgdel = 0;
	cl->adc_violations = 0;

	if (adc == -1) {
		cl->concerned_adc = 0;
		adc = ALTQ_INFINITY;
	} else 
		cl->concerned_adc = 1;

	if (alc == -1) {
		cl->concerned_alc = 0;
		alc = ALTQ_INFINITY;
	} else 
		cl->concerned_alc = 1;

	if (rdc == -1) {
		rdc = 0;
		cl->concerned_rdc = 0;
	} else 
		cl->concerned_rdc = 1;

	if (rlc == -1) {
		rlc = 0;
		cl->concerned_rlc = 0;
	} else 
		cl->concerned_rlc = 1;

	if (arc == -1) {
		arc = 0;
		cl->concerned_arc = 0;
	} else
		cl->concerned_arc = 1;

	cl->cl_rdc=rdc;

	if (cl->concerned_adc) {
		/* adc is given in us, convert it to clock ticks */
		cl->cl_adc = (u_int64_t)(adc*machclk_freq/GRANULARITY);
	} else
		cl->cl_adc = adc;

	if (cl->concerned_arc) {
		/* arc is given in bps, convert it to internal unit */
		cl->cl_arc = (u_int64_t)(bps_to_internal(arc));
	} else
		cl->cl_arc = arc;

	cl->cl_rlc=rlc;
	cl->cl_alc=alc;
	cl->delay_prod_others = 0;
	cl->loss_prod_others = 0;
	cl->cl_flags = flags;
	cl->cl_pri = pri;
	if (pri > jif->jif_maxpri)
		jif->jif_maxpri = pri;
	cl->cl_jif = jif;
	cl->cl_handle = (u_long)cl;  /* just a pointer to this class */

	/*
	 * update delay_prod_others and loss_prod_others
	 * in all classes if needed
	 */

	if (cl->concerned_rdc) {
		for (i = 0; i <= jif->jif_maxpri; i++) {
			scan1 = jif->jif_classes[i];
			class_exists1 = (scan1 != NULL);
			if (class_exists1) {
				tmp[i] = 1;
				for (j = 0; j <= i-1; j++) {
					scan2 = jif->jif_classes[j];
					class_exists2 = (scan2 != NULL);
					if (class_exists2
					    && scan2->concerned_rdc)
						tmp[i] *= scan2->cl_rdc;
				}
			} else
				tmp[i] = 0;
		}

		for (i = 0; i <= jif->jif_maxpri; i++) {
			scan1 = jif->jif_classes[i];
			class_exists1 = (scan1 != NULL);
			if (class_exists1) {
				scan1->delay_prod_others = 1;
				for (j = 0; j <= jif->jif_maxpri; j++) {
					scan2 = jif->jif_classes[j];
					class_exists2 = (scan2 != NULL);
					if (class_exists2 && j != i
					    && scan2->concerned_rdc)
						scan1->delay_prod_others *= tmp[j];
				}
			}
		}
	}

	if (cl->concerned_rlc) {
		for (i = 0; i <= jif->jif_maxpri; i++) {
			scan1 = jif->jif_classes[i];
			class_exists1 = (scan1 != NULL);
			if (class_exists1) {
				tmp[i] = 1;
				for (j = 0; j <= i-1; j++) {
					scan2 = jif->jif_classes[j];
					class_exists2 = (scan2 != NULL);
					if (class_exists2
					    && scan2->concerned_rlc)
						tmp[i] *= scan2->cl_rlc;
				}
			} else
				tmp[i] = 0;
		}

		for (i = 0; i <= jif->jif_maxpri; i++) {
			scan1 = jif->jif_classes[i];
			class_exists1 = (scan1 != NULL);
			if (class_exists1) {
				scan1->loss_prod_others = 1;
				for (j = 0; j <= jif->jif_maxpri; j++) {
					scan2 = jif->jif_classes[j];
					class_exists2 = (scan2 != NULL);
					if (class_exists2 && j != i
					    && scan2->concerned_rlc)
						scan1->loss_prod_others *= tmp[j];
				}
			}
		}
	}

	now = read_machclk();
	cl->idletime = now;
	return cl;

 err_ret:
	if (cl->cl_q != NULL)
		free(cl->cl_q, M_DEVBUF);
	if (cl->arv_tm != NULL)
		free(cl->arv_tm, M_DEVBUF);

	free(cl, M_DEVBUF);
	return NULL;
}

static int
jobs_class_destroy(struct jobs_class *cl)
{
	struct jobs_if *jif;
	int s, pri;

	s = splnet();

	/* delete filters referencing to this class */
	acc_discard_filters(&cl->cl_jif->jif_classifier, cl, 0);

	if (!qempty(cl->cl_q))
		jobs_purgeq(cl);

	jif = cl->cl_jif;
	jif->jif_classes[cl->cl_pri] = NULL;
	if (jif->jif_maxpri == cl->cl_pri) {
		for (pri = cl->cl_pri; pri >= 0; pri--)
			if (jif->jif_classes[pri] != NULL) {
				jif->jif_maxpri = pri;
				break;
			}
		if (pri < 0)
			jif->jif_maxpri = -1;
	}
	splx(s);

	tslist_destroy(cl);
	free(cl->cl_q, M_DEVBUF);
	free(cl, M_DEVBUF);
	return 0;
}

/*
 * jobs_enqueue is an enqueue function to be registered to
 * (*altq_enqueue) in struct ifaltq.
 */
static int
jobs_enqueue(struct ifaltq *ifq, struct mbuf *m)
{
	struct jobs_if	*jif = (struct jobs_if *)ifq->altq_disc;
	struct jobs_class *cl, *scan;
	int len;
	int return_flag;
	int pri;
	u_int64_t now;
	u_int64_t old_arv;
	int64_t* delta_rate;
	u_int64_t tstamp1, tstamp2, cycles; /* used for benchmarking only */

	jif->total_enqueued++;
	now = read_machclk();
	tstamp1 = now;

	return_flag = 0;

	/* proceed with packet enqueuing */

	if (IFQ_IS_EMPTY(ifq)) {
		for (pri=0; pri <= jif->jif_maxpri; pri++) {
			scan = jif->jif_classes[pri];
			if (scan != NULL) {
				/*
				 * reset all quantities, except:
				 * average delay, number of violations
				 */
				PKTCNTR_RESET(&scan->cl_rin);
				PKTCNTR_RESET(&scan->cl_rout);
				PKTCNTR_RESET(&scan->cl_rout_th);
				PKTCNTR_RESET(&scan->cl_arrival);
				PKTCNTR_RESET(&scan->cl_dropcnt);	
				scan->cl_lastdel = 0;
				scan->current_loss = 0;
				scan->service_rate = 0;
				scan->idletime = now;
				scan->cl_last_rate_update = now;
			}
		}
	}

	/* grab class set by classifier */
	if ((cl = m->m_pkthdr.pattr_class) == NULL)
		cl = jif->jif_default;

	len = m_pktlen(m);
	old_arv = cl->cl_arrival.bytes;
	PKTCNTR_ADD(&cl->cl_arrival, (int)len);
	PKTCNTR_ADD(&cl->cl_rin, (int)len);
	PKTCNTR_ADD(&cl->st_arrival, (int)len);
	PKTCNTR_ADD(&cl->st_rin, (int)len);

	if (cl->cl_arrival.bytes < old_arv) {
		/* deals w/ overflow */
		for (pri=0; pri <= jif->jif_maxpri; pri++) {
			scan = jif->jif_classes[pri];
			if (scan != NULL) {
				/*
				 * reset all quantities, except:
				 * average delay, number of violations
				 */
				PKTCNTR_RESET(&scan->cl_rin);
				PKTCNTR_RESET(&scan->cl_rout);
				PKTCNTR_RESET(&scan->cl_rout_th);
				PKTCNTR_RESET(&scan->cl_arrival);
				PKTCNTR_RESET(&scan->cl_dropcnt);	
				scan->current_loss = 0;
				scan->service_rate = 0;
				scan->idletime = now;
				scan->cl_last_rate_update = now;
			}
		}
		PKTCNTR_ADD(&cl->cl_arrival, (int)len);
		PKTCNTR_ADD(&cl->cl_rin, (int)len);
	}

	if (cl->cl_arrival.bytes > cl->cl_rin.bytes)
		cl->current_loss =
		    ((cl->cl_arrival.bytes - cl->cl_rin.bytes) << SCALE_LOSS)
		    / cl->cl_arrival.bytes;
	else
		cl->current_loss = 0;

	/* for MDRR: update theoretical value of the output curve */

	for (pri=0; pri <= jif->jif_maxpri; pri++) {
		scan = jif->jif_classes[pri];
		if (scan != NULL) {
			if (scan->cl_last_rate_update == scan->idletime
			    || scan->cl_last_rate_update == 0)
				scan->cl_last_rate_update = now; /* initial case */
			else
				scan->cl_rout_th.bytes +=
				    delay_diff(now, scan->cl_last_rate_update)
				    * scan->service_rate;

			/*
			 * we don't really care about packets here
			 * WARNING: rout_th is SCALED
			 * (b/c of the service rate)
			 * for precision, as opposed to rout.
			 */

			scan->cl_last_rate_update = now;
		}
	}

	if (jobs_addq(cl, m, jif) != 0)
		return_flag = ENOBUFS; /* signals there's a buffer overflow */
	else
		IFQ_INC_LEN(ifq);

	/* successfully queued. */

	enforce_wc(jif);

	if (!min_rates_adc(jif)) {
		delta_rate = assign_rate_drops_adc(jif);
		if (delta_rate != NULL) {
			for (pri = 0; pri <= jif->jif_maxpri; pri++)
			  if ((cl = jif->jif_classes[pri]) != NULL &&
			      !qempty(cl->cl_q))
				cl->service_rate += delta_rate[pri];
			free(delta_rate, M_DEVBUF);
		}
	}

	delta_rate = adjust_rates_rdc(jif);

	if (delta_rate != NULL) {
		for (pri = 0; pri <= jif->jif_maxpri; pri++)
			if ((cl = jif->jif_classes[pri]) != NULL &&
			    !qempty(cl->cl_q))
				cl->service_rate += delta_rate[pri];
		free(delta_rate, M_DEVBUF);
	}

	tstamp2 = read_machclk();
	cycles = delay_diff(tstamp2, tstamp1);
	if (cycles > jif->wc_cycles_enqueue)
		jif->wc_cycles_enqueue=cycles;
	if (cycles < jif->bc_cycles_enqueue)
		jif->bc_cycles_enqueue=cycles;

	jif->avg_cycles_enqueue += cycles;
	jif->avg_cycles2_enqueue += cycles * cycles;

	return return_flag;
}

/*
 * jobs_dequeue is a dequeue function to be registered to
 * (*altq_dequeue) in struct ifaltq.
 *
 * note: ALTDQ_POLL returns the next packet without removing the packet
 *	from the queue.  ALTDQ_REMOVE is a normal dequeue operation.
 *	ALTDQ_REMOVE must return the same packet if called immediately
 *	after ALTDQ_POLL.
 */

static struct mbuf *
jobs_dequeue(struct ifaltq *ifq, int op)
{
	struct jobs_if	*jif = (struct jobs_if *)ifq->altq_disc;
	struct jobs_class *cl;
	struct mbuf *m;
	int pri;
	int svc_class;
	int64_t max_error;
	int64_t error;
	u_int64_t now;
	u_int64_t tstamp1, tstamp2, cycles;

	jif->total_dequeued++;

	now = read_machclk();
	tstamp1 = now;

	if (IFQ_IS_EMPTY(ifq)) {
		/* no packet in the queue */
		for (pri=0; pri <= jif->jif_maxpri; pri++) {
		  cl = jif->jif_classes[pri];
		  if (cl != NULL)
		    cl->idletime = now;
		}

		tstamp2 = read_machclk();
		cycles = delay_diff(tstamp2, tstamp1);
		if (cycles > jif->wc_cycles_dequeue)
			jif->wc_cycles_dequeue = cycles;
		if (cycles < jif->bc_cycles_dequeue)
			jif->bc_cycles_dequeue = cycles;

		jif->avg_cycles_dequeue += cycles;
		jif->avg_cycles2_dequeue += cycles * cycles;

		return NULL;
	}

	/*
	 * select the class whose actual tranmissions are the furthest
	 * from the promised transmissions
	 */

	max_error = -1;
	svc_class = -1;

	for (pri=0; pri <= jif->jif_maxpri; pri++) {
		if (((cl = jif->jif_classes[pri]) != NULL)
		    && !qempty(cl->cl_q)) {
			error = (int64_t)cl->cl_rout_th.bytes
			    -(int64_t)scale_rate(cl->cl_rout.bytes);
			if (max_error == -1) {
				max_error = error;
				svc_class = pri;
			} else if (error > max_error) {
				max_error = error;
				svc_class = pri;
			}
		}
	}

	if (svc_class != -1)
		cl = jif->jif_classes[svc_class];
	else
		cl = NULL;

	if (op == ALTDQ_POLL) {
		tstamp2 = read_machclk();
		cycles = delay_diff(tstamp2, tstamp1);
		if (cycles > jif->wc_cycles_dequeue)
			jif->wc_cycles_dequeue = cycles;
		if (cycles < jif->bc_cycles_dequeue)
			jif->bc_cycles_dequeue = cycles;

		jif->avg_cycles_dequeue += cycles;
		jif->avg_cycles2_dequeue += cycles * cycles;

		return (jobs_pollq(cl));
	}

	if (cl != NULL)
		m = jobs_getq(cl);
	else
		m = NULL;

	if (m != NULL) {
		IFQ_DEC_LEN(ifq);
		if (qempty(cl->cl_q))
			cl->cl_period++;

		cl->cl_lastdel = (u_int64_t)delay_diff(now,
		    tslist_first(cl->arv_tm)->timestamp);
		if (cl->concerned_adc
		    && (int64_t)cl->cl_lastdel > cl->cl_adc)
			cl->adc_violations++;
		cl->cl_avgdel  += ticks_to_secs(GRANULARITY*cl->cl_lastdel);

		PKTCNTR_ADD(&cl->cl_rout, m_pktlen(m));
		PKTCNTR_ADD(&cl->st_rout, m_pktlen(m));
	}
	if (cl != NULL)
		tslist_dequeue(cl);		/* dequeue the timestamp */

	tstamp2 = read_machclk();
	cycles = delay_diff(tstamp2, tstamp1);
	if (cycles > jif->wc_cycles_dequeue)
		jif->wc_cycles_dequeue = cycles;
	if (cycles < jif->bc_cycles_dequeue)
		jif->bc_cycles_dequeue = cycles;

	jif->avg_cycles_dequeue += cycles;
	jif->avg_cycles2_dequeue += cycles * cycles;

	return m;
}

static int
jobs_addq(struct jobs_class *cl, struct mbuf *m, struct jobs_if *jif)
{
	int victim;
	u_int64_t len;
	u_int64_t now;
	struct jobs_class* victim_class;

	victim = -1;
	victim_class = NULL;
	len = 0;

	now = read_machclk();

	if (jif->jif_separate && qlen(cl->cl_q) >= jif->jif_qlimit) {
		/*
		 * separate buffers: no guarantees on packet drops
		 * can be offered
		 * thus we drop the incoming packet
		 */
		len = (u_int64_t)m_pktlen(m);
		PKTCNTR_ADD(&cl->cl_dropcnt, (int)len);
		PKTCNTR_SUB(&cl->cl_rin, (int)len);
		PKTCNTR_ADD(&cl->st_dropcnt, (int)len);
		PKTCNTR_SUB(&cl->st_rin, (int)len);
		cl->current_loss += (len << SCALE_LOSS)
		    /cl->cl_arrival.bytes;
		m_freem(m);
		return (-1);

	} else if (!jif->jif_separate
		   && jif->jif_ifq->ifq_len >= jif->jif_qlimit) {
		/* shared buffer: supports guarantees on losses */
		if (!cl->concerned_rlc) {
			if (!cl->concerned_alc) {
				/*
				 * no ALC, no RLC on this class:
				 * drop the incoming packet
				 */
				len = (u_int64_t)m_pktlen(m);
				PKTCNTR_ADD(&cl->cl_dropcnt, (int)len);
				PKTCNTR_SUB(&cl->cl_rin, (int)len);
				PKTCNTR_ADD(&cl->st_dropcnt, (int)len);
				PKTCNTR_SUB(&cl->st_rin, (int)len);
				cl->current_loss += (len << SCALE_LOSS)/cl->cl_arrival.bytes;
				m_freem(m);
				return (-1);
			} else {
				/*
				 * no RLC, but an ALC:
				 * drop the incoming packet if possible
				 */
				len = (u_int64_t)m_pktlen(m);
				if (cl->current_loss + (len << SCALE_LOSS)
				    / cl->cl_arrival.bytes <= cl->cl_alc) {
					PKTCNTR_ADD(&cl->cl_dropcnt, (int)len);
					PKTCNTR_SUB(&cl->cl_rin, (int)len);
					PKTCNTR_ADD(&cl->st_dropcnt, (int)len);
					PKTCNTR_SUB(&cl->st_rin, (int)len);
					cl->current_loss += (len << SCALE_LOSS)/cl->cl_arrival.bytes;
					m_freem(m);
					return (-1);
				} else {
					/*
					 * the ALC would be violated:
					 * pick another class
					 */
					_addq(cl->cl_q, m);
					tslist_enqueue(cl, now);

					victim = pick_dropped_rlc(jif);

					if (victim == -1) {
						/*
						 * something went wrong
						 * let us discard
						 * the incoming packet,
						 * regardless of what
						 * may happen...
						 */
						victim_class = cl;
					} else
						victim_class = jif->jif_classes[victim];

					if (victim_class != NULL) {
						/*
						 * test for safety
						 * purposes...
						 * it must be true
						 */
						m = _getq_tail(victim_class->cl_q);
						len = (u_int64_t)m_pktlen(m);
						PKTCNTR_ADD(&victim_class->cl_dropcnt, (int)len);
						PKTCNTR_SUB(&victim_class->cl_rin, (int)len);
						PKTCNTR_ADD(&victim_class->st_dropcnt, (int)len);
						PKTCNTR_SUB(&victim_class->st_rin, (int)len);
						victim_class->current_loss += (len << SCALE_LOSS)/victim_class->cl_arrival.bytes;
						m_freem(m); /* the packet is trashed here */
						tslist_drop(victim_class); /* and its timestamp as well */
					}
					return (-1);
				}
			}
		} else {
			/*
			 * RLC on that class:
			 * pick class according to RLCs
			 */
			_addq(cl->cl_q, m);
			tslist_enqueue(cl, now);

			victim = pick_dropped_rlc(jif);
			if (victim == -1) {
				/*
				 * something went wrong
				 * let us discard the incoming packet,
				 * regardless of what may happen...
				 */
				victim_class = cl;
			} else
				victim_class = jif->jif_classes[victim];

			if (victim_class != NULL) {
				/*
				 * test for safety purposes...
				 * it must be true
				 */
				m = _getq_tail(victim_class->cl_q);
				len = (u_int64_t)m_pktlen(m);
				PKTCNTR_ADD(&victim_class->cl_dropcnt, (int)len);
				PKTCNTR_SUB(&victim_class->cl_rin, (int)len);
				PKTCNTR_ADD(&victim_class->st_dropcnt, (int)len);
				PKTCNTR_SUB(&victim_class->st_rin, (int)len);
				victim_class->current_loss += (len << SCALE_LOSS)/victim_class->cl_arrival.bytes;
				m_freem(m); /* the packet is trashed here */
				tslist_drop(victim_class); /* and its timestamp as well */
			}
			return -1;
		}
	}
	/* else: no drop */

	_addq(cl->cl_q, m);
	tslist_enqueue(cl, now);

	return 0;
}

static struct mbuf *
jobs_getq(struct jobs_class *cl)
{
	return _getq(cl->cl_q);
}

static struct mbuf *
jobs_pollq(struct jobs_class *cl)
{
	return qhead(cl->cl_q);
}

static void
jobs_purgeq(struct jobs_class *cl)
{
	struct mbuf *m;

	if (qempty(cl->cl_q))
		return;

	while ((m = _getq(cl->cl_q)) != NULL) {
		PKTCNTR_ADD(&cl->cl_dropcnt, m_pktlen(m));
		PKTCNTR_ADD(&cl->st_dropcnt, m_pktlen(m));
		m_freem(m);
		tslist_drop(cl);
	}
	ASSERT(qlen(cl->cl_q) == 0);
}

/*
 * timestamp list support routines
 *
 * this implementation has been revamped and
 * now uses a TAILQ structure.
 * timestamp list holds class timestamps
 * there is one timestamp list per class.
 */
static TSLIST *
tslist_alloc(void)
{
	TSLIST *list_init;

	list_init = malloc(sizeof(TSLIST), M_DEVBUF, M_WAITOK);
	TAILQ_INIT(list_init);
	return list_init;
}

static void
tslist_destroy(struct jobs_class *cl)
{
	while (tslist_first(cl->arv_tm) != NULL)
		tslist_dequeue(cl);

	free(cl->arv_tm, M_DEVBUF);
}

static int
tslist_enqueue(struct jobs_class *cl, u_int64_t arv)
{
	TSENTRY *pushed;
	pushed = malloc(sizeof(TSENTRY), M_DEVBUF, M_WAITOK);
	if (pushed == NULL)
		return 0;	

	pushed->timestamp = arv;
	TAILQ_INSERT_TAIL(cl->arv_tm, pushed, ts_list);
	return 1;
}

static void
tslist_dequeue(struct jobs_class *cl)
{
	TSENTRY *popped;
	popped = tslist_first(cl->arv_tm);
	if (popped != NULL) {
		  TAILQ_REMOVE(cl->arv_tm, popped, ts_list);
		  free(popped, M_DEVBUF);
	}
	return;
}

static void
tslist_drop(struct jobs_class *cl)
{
	TSENTRY *popped;
	popped = tslist_last(cl->arv_tm);
	if (popped != NULL) {
		  TAILQ_REMOVE(cl->arv_tm, popped, ts_list);
		  free(popped, M_DEVBUF);
	}
	return;
}

/*
 * rate allocation support routines
 */
/*
 * enforce_wc: enforce that backlogged classes have non-zero
 * service rate, and that non-backlogged classes have zero
 * service rate.
 */

static int
enforce_wc(struct jobs_if *jif)
{
	struct jobs_class *cl;

	int64_t active_classes;
	int pri;
	int is_backlogged, class_exists, updated;

	updated = 0;
	active_classes = 0;

	for (pri = 0; pri <= jif->jif_maxpri; pri++) {
		cl = jif->jif_classes[pri];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged)
			active_classes++;
		if ((is_backlogged && cl->service_rate <= 0)
		    ||(class_exists
		       && !is_backlogged && cl->service_rate > 0))
			updated = 1;
	}

	if (updated) {
		for (pri = 0; pri <= jif->jif_maxpri; pri++) {
			cl = jif->jif_classes[pri];
			class_exists = (cl != NULL);
			is_backlogged = (class_exists && !qempty(cl->cl_q));

			if (class_exists && !is_backlogged)
				cl->service_rate = 0;
			else if (is_backlogged)
				cl->service_rate = (int64_t)(bps_to_internal((u_int64_t)jif->jif_bandwidth)/active_classes);
		}
	}

	return (updated);
}

/*
 * adjust_rates_rdc: compute the service rates adjustments
 * needed to realize the desired proportional delay differentiation.
 * essentially, the rate adjustement delta_rate = prop_control*error,
 * where error is the difference between the measured "weighted"
 * delay and the mean of the weighted delays. see paper for more
 * information.
 * prop_control has slightly changed since the INFOCOM paper,
 * this condition seems to provide better results.
 */

static int64_t *
adjust_rates_rdc(struct jobs_if *jif)
{
	int64_t *result;
	int64_t credit, available, lower_bound, upper_bound;
	int64_t bk;
	int i, j;
	int rdc_classes, active_classes;
	int class_exists, is_backlogged;
	struct jobs_class *cl;
	int64_t *error;
	int64_t prop_control;
	u_int64_t max_prod;
	u_int64_t min_share;
	u_int64_t max_avg_pkt_size;

	/*
	 * min_share is scaled
	 * to avoid dealing with doubles
	 */
	active_classes = 0;
	rdc_classes = 0;
	max_prod = 0;
	max_avg_pkt_size = 0;

	upper_bound = (int64_t)jif->jif_bandwidth;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged) {
			active_classes++;
			if (cl->concerned_rdc)
				rdc_classes++;
			else
				upper_bound -=
				    internal_to_bps(cl->service_rate);
		}
	}

	result = malloc((jif->jif_maxpri+1)*sizeof(int64_t),
	    M_DEVBUF, M_WAITOK);

	if (result == NULL)
		return NULL;

	for (i = 0; i <= jif->jif_maxpri; i++)
		result[i] = 0;

	if (upper_bound <= 0 || rdc_classes == 0)
		return result;

	credit = 0;
	lower_bound = 0;
	min_share = ((u_int64_t)1 << SCALE_SHARE);
	bk = 0;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged && cl->concerned_rdc)
			bk += cl->cl_rin.bytes;
	}

	if (bk == 0)
		return result;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged
		    && (cl->cl_rin.bytes << SCALE_SHARE)/bk < min_share)
			min_share = (cl->cl_rin.bytes << SCALE_SHARE)/bk;
		if (is_backlogged && cl->concerned_rdc
		    && cl->delay_prod_others > max_prod)
			max_prod = cl->delay_prod_others;

		if (is_backlogged && cl->concerned_rdc
		    && cl->cl_rin.bytes > max_avg_pkt_size*cl->cl_rin.packets)
			max_avg_pkt_size = (u_int64_t)((u_int)cl->cl_rin.bytes/(u_int)cl->cl_rin.packets);
	}

	error = update_error(jif);
	if (!error)
		goto fail;

	prop_control = (upper_bound*upper_bound*min_share)
	    /(max_prod*(max_avg_pkt_size << 2));
  
	prop_control = bps_to_internal(ticks_to_secs(prop_control)); /* in BT-1 */

	credit = 0;
	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged && cl->concerned_rdc) {
			result[i] = -prop_control*error[i]; /* in BT-1 */
			result[i] >>= (SCALE_SHARE);
		}
	}

	free(error, M_DEVBUF); /* we don't need these anymore */

	/* saturation */

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged && cl->concerned_rdc)
			lower_bound += cl->min_rate_adc;
		/*
		 * note: if there's no ADC or ARC on cl,
		 * this is equal to zero, which is fine
		 */
	}

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged && cl->concerned_rdc
		    && result[i] + cl->service_rate > upper_bound) {
			for (j = 0; j <= jif->jif_maxpri; j++) {
				cl = jif->jif_classes[j];
				class_exists = (cl != NULL);
				is_backlogged = (class_exists
						 && !qempty(cl->cl_q));
				if (is_backlogged && cl->concerned_rdc) {
					if (j == i)
						result[j] = upper_bound
						    -cl->service_rate
						    + cl->min_rate_adc
						    - lower_bound;
					else
						result[j] =
						    -cl->service_rate
						    +cl->min_rate_adc;
				}
			}
			return result;
		}

		cl = jif->jif_classes[i];
		/* do this again since it may have been modified */
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged && cl->concerned_rdc
		    && result[i] + cl->service_rate < cl->min_rate_adc) {
			credit += cl->service_rate+result[i]
			    -cl->min_rate_adc;
			/* "credit" is in fact a negative number */
			result[i] = -cl->service_rate+cl->min_rate_adc;
		}
	}

	for (i = jif->jif_maxpri; (i >= 0 && credit < 0); i--) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
 
		if (is_backlogged && cl->concerned_rdc) {
			available = result[i]
			    + cl->service_rate-cl->min_rate_adc;
			if (available >= -credit) {
				result[i] += credit;
				credit = 0;
			} else {
				result[i] -= available;
				credit += available;
			}
		}
	}
	return result;

fail:	free(result, M_DEVBUF);
	return NULL;
}

/*
 * assign_rate_drops_adc: returns the adjustment needed to
 * the service rates to meet the absolute delay/rate constraints
 * (delay/throughput bounds) and drops traffic if need be.
 * see tech. report UVA/T.R. CS-2000-24/CS-2001-21 for more info.
 */

static int64_t *
assign_rate_drops_adc(struct jobs_if *jif)
{
	int64_t *result;
	int class_exists, is_backlogged;
	struct jobs_class *cl;

	int64_t *c, *n, *k;
	int64_t *available;

	int lowest, highest;
	int keep_going;
	int i;
	u_int64_t now, oldest_arv;
	int64_t	remaining_time;
	struct mbuf* pkt;
	u_int64_t len;

	now = read_machclk();
	oldest_arv = now;

	result = malloc((jif->jif_maxpri+1)*sizeof(int64_t), M_DEVBUF, M_WAITOK);
	if (result == NULL)
		goto fail0;
	c = malloc((jif->jif_maxpri+1)*sizeof(u_int64_t), M_DEVBUF, M_WAITOK);
	if (c == NULL)
		goto fail1;
	n = malloc((jif->jif_maxpri+1)*sizeof(u_int64_t), M_DEVBUF, M_WAITOK);
	if (n == NULL)
		goto fail2;
	k = malloc((jif->jif_maxpri+1)*sizeof(u_int64_t), M_DEVBUF, M_WAITOK);
	if (k == NULL)
		goto fail3;
	available = malloc((jif->jif_maxpri+1)*sizeof(int64_t), M_DEVBUF, M_WAITOK);
	if (available == NULL)
		goto fail4;

	for (i = 0; i <= jif->jif_maxpri; i++)
		result[i] = 0;

	keep_going = 1;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged) {
			if (cl->concerned_adc) {
				/*
				 * get the arrival time of the oldest
				 * class-i packet
				 */
				if (tslist_first(cl->arv_tm) == NULL)
					oldest_arv = now; /* NOTREACHED */
				else
					oldest_arv = (tslist_first(cl->arv_tm))->timestamp;

				n[i] = cl->service_rate;
				k[i] = scale_rate((int64_t)(cl->cl_rin.bytes - cl->cl_rout.bytes));

				remaining_time = cl->cl_adc
				    - (int64_t)delay_diff(now, oldest_arv);
				if (remaining_time > 0) {
					c[i] = remaining_time;
					/*
					 * c is the remaining time before
					 * the deadline is violated
					 * (in ticks)
					 */
					available[i] = n[i]-k[i]/c[i];
				} else {
					/*
					 * deadline has passed...
					 * we allocate the whole link
					 * capacity to hopefully
					 * solve the problem
					 */
					c[i] = 0;
					available[i] = -((int64_t)bps_to_internal((u_int64_t)jif->jif_bandwidth));
				}
				if (cl->concerned_arc) {
					/*
					 * there's an ARC in addition
					 * to the ADC
					 */
					if (n[i] - cl->cl_arc < available[i])
						available[i] = n[i]
						    - cl->cl_arc;
				}
			} else if (cl->concerned_arc) {
				/*
				 * backlogged, concerned by ARC
				 * but not by ADC
				 */
				n[i] = cl->service_rate;
				available[i] = n[i] - cl->cl_arc;
			} else {
				/*
				 * backlogged but not concerned by ADC
				 * or ARC -> can give everything
				 */
				n[i] = cl->service_rate;
				available[i] = n[i];
			}
		} else {
			/* not backlogged */
			n[i] = 0;
			k[i] = 0;
			c[i] = 0;
			if (class_exists)
				available[i] = cl->service_rate;
			else
				available[i] = 0;
		}
	}

	/* step 1: adjust rates (greedy algorithm) */

	highest = 0;
	lowest  = jif->jif_maxpri;

	while (highest < jif->jif_maxpri+1 && available[highest] >= 0)
		highest++; /* which is the highest class that needs more service? */
	while (lowest > 0 && available[lowest] <= 0)
		lowest--;  /* which is the lowest class that needs less service? */

	while (highest != jif->jif_maxpri+1 && lowest != -1) {
		/* give the excess service from lowest to highest */
		if (available[lowest]+available[highest] > 0) {
			/*
			 * still some "credit" left
			 * give all that is needed by "highest"
			 */
			n[lowest]  += available[highest];
			n[highest] -= available[highest];
			available[lowest]  += available[highest];
			available[highest] = 0;

			while (highest < jif->jif_maxpri+1
			       && available[highest] >= 0)
				highest++;  /* which is the highest class that needs more service now? */

		} else if (available[lowest]+available[highest] == 0) {
			/* no more credit left but it's fine */
			n[lowest]  += available[highest];
			n[highest] -= available[highest];
			available[highest] = 0;
			available[lowest]  = 0;

			while (highest < jif->jif_maxpri+1
			       && available[highest] >= 0)
				highest++;  /* which is the highest class that needs more service? */
			while (lowest >= 0 && available[lowest] <= 0)
				lowest--;   /* which is the lowest class that needs less service? */

		} else if (available[lowest]+available[highest] < 0) {
			/*
			 * no more credit left and we need to switch
			 * to another class
			 */
			n[lowest]  -= available[lowest];
			n[highest] += available[lowest];
			available[highest] += available[lowest];
			available[lowest]  = 0;

			while ((lowest >= 0)&&(available[lowest] <= 0))
				lowest--;  /* which is the lowest class that needs less service? */
		}
	}

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged) {
			result[i] = n[i] - cl->service_rate;
		} else {
			if (class_exists)
				result[i] = - cl->service_rate;
			else
				result[i] = 0;
		}
	}

	/* step 2: adjust drops (for ADC) */

	if (highest != jif->jif_maxpri+1) {
		/* some class(es) still need(s) additional service */
		for (i = 0; i <= jif->jif_maxpri; i++) {
			cl = jif->jif_classes[i];
			class_exists = (cl != NULL);
			is_backlogged = (class_exists
					 && !qempty(cl->cl_q));
			if (is_backlogged && available[i] < 0) {
				if (cl->concerned_adc) {
					k[i] = c[i]*n[i];
					while (keep_going && scale_rate((int64_t)(cl->cl_rin.bytes-cl->cl_rout.bytes)) > k[i]) {
						pkt = qtail(cl->cl_q);
						if (pkt != NULL) {
							/* "safeguard" test (a packet SHOULD be in there) */
							len = (u_int64_t)m_pktlen(pkt);
							/* access packet at the tail */
							if (cl->concerned_alc
							    && cl->current_loss+(len << SCALE_LOSS)/cl->cl_arrival.bytes > cl->cl_alc) {
								keep_going = 0; /* relax ADC in favor of ALC */
							} else {
								/* drop packet at the tail of the class-i queue, update values */
								pkt = _getq_tail(cl->cl_q);
								len = (u_int64_t)m_pktlen(pkt);
								PKTCNTR_ADD(&cl->cl_dropcnt, (int)len);
								PKTCNTR_SUB(&cl->cl_rin, (int)len);
								PKTCNTR_ADD(&cl->st_dropcnt, (int)len);
								PKTCNTR_SUB(&cl->st_rin, (int)len);
								cl->current_loss += (len << SCALE_LOSS)/cl->cl_arrival.bytes;
								m_freem(pkt); /* the packet is trashed here */
								tslist_drop(cl);
								IFQ_DEC_LEN(cl->cl_jif->jif_ifq);
							}
						} else
							keep_going = 0; /* NOTREACHED */
					}
					k[i] = scale_rate((int64_t)(cl->cl_rin.bytes-cl->cl_rout.bytes));
				}
				/*
				 * n[i] is the max rate we can give.
				 * the above drops as much as possible
				 * to respect a delay bound.
				 * for throughput bounds,
				 * there's nothing that can be done
				 * after the greedy reallocation.
				 */
			}
		}
	}

	/* update the values of min_rate_adc */
	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged && cl->concerned_adc) {
			if (c[i] != 0) {
				if (cl->concerned_adc
				    && !cl->concerned_arc)
					cl->min_rate_adc = k[i]/c[i];
				else
					cl->min_rate_adc = n[i];
			} else
				cl->min_rate_adc = (int64_t)bps_to_internal((u_int64_t)jif->jif_bandwidth);
		} else if (is_backlogged && cl->concerned_arc)
			cl->min_rate_adc = n[i]; /* the best we can give */
		else {
			if (class_exists)
				cl->min_rate_adc = 0;
		}
	}

	free(c, M_DEVBUF);
	free(n, M_DEVBUF);
	free(k, M_DEVBUF);
	free(available, M_DEVBUF);

	return result;

fail5: __unused
	free(available, M_DEVBUF);
fail4:	free(k, M_DEVBUF);
fail3:	free(n, M_DEVBUF);
fail2:	free(c, M_DEVBUF);
fail1:	free(result, M_DEVBUF);
fail0:	return NULL;
}

/*
 * update_error: returns the difference between the mean weighted
 * delay and the weighted delay for each class. if proportional
 * delay differentiation is perfectly achieved, it should return
 * zero for each class.
 */
static int64_t *
update_error(struct jobs_if *jif)
{
	int i;
	int active_classes;
	u_int64_t mean_weighted_delay;
	u_int64_t delays[JOBS_MAXPRI];
	int64_t* error;
	int class_exists, is_backlogged;
	struct jobs_class *cl;

	error = malloc(sizeof(int64_t)*(jif->jif_maxpri+1), M_DEVBUF,
	    M_WAITOK|M_ZERO);

	if (error == NULL)
		return NULL;

	mean_weighted_delay = 0;
	active_classes = 0;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged) {
			if (cl->concerned_rdc) {
				delays[i] = proj_delay(jif, i);
				mean_weighted_delay += cl->delay_prod_others*delays[i];
				active_classes ++;
			}
		}
	}

	if (active_classes == 0)
		return error;
	else
		mean_weighted_delay /= active_classes;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));

		if (is_backlogged && cl->concerned_rdc)
			error[i] = ((int64_t)mean_weighted_delay)-((int64_t)cl->delay_prod_others*delays[i]);
		else
			error[i] = 0; /*
				       * either the class isn't concerned,
				       * or it's not backlogged.
				       * in any case, the rate shouldn't
				       * be adjusted.
				       */
	}
	return error;
}

/*
 * min_rates_adc: computes the minimum service rates needed in
 * each class to meet the absolute delay bounds. if, for any
 * class i, the current service rate of class i is less than
 * the computed minimum service rate, this function returns
 * false, true otherwise.
 */
static int
min_rates_adc(struct jobs_if *jif)
{
	int result;
	int i;
	int class_exists, is_backlogged;
	int64_t remaining_time;
	struct jobs_class *cl;
	result = 1;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged && cl->concerned_adc) { 
			remaining_time = cl->cl_adc - proj_delay(jif, i);
			if (remaining_time > 0 ) {
				/* min rate needed for ADC */
				cl->min_rate_adc = scale_rate((int64_t)(cl->cl_rin.bytes-cl->cl_rout.bytes))/remaining_time;
				if (cl->concerned_arc
				    && cl->cl_arc > cl->min_rate_adc) {
					/* min rate needed for ADC + ARC */
					cl->min_rate_adc = cl->cl_arc;
				}
			} else {
				/* the deadline has been exceeded: give the whole link capacity to hopefully fix the situation */
				cl->min_rate_adc = (int64_t)bps_to_internal((u_int64_t)jif->jif_bandwidth);
			}
      		} else if (is_backlogged && cl->concerned_arc)
			cl->min_rate_adc = cl->cl_arc; 			/* no ADC, an ARC */
		else if (class_exists)
			cl->min_rate_adc = 0;	/*
						 * either the class is not
						 * backlogged
						 * or there is no ADC and
						 * no ARC
						 */
		if (is_backlogged && cl->min_rate_adc > cl->service_rate)
			result = 0;
	}

	return result;
}

/*
 * proj_delay: computes the difference between the current time
 * and the time the oldest class-i packet still in the class-i
 * queue i arrived in the system.
 */
static int64_t
proj_delay(struct jobs_if *jif, int i)
{
	u_int64_t now;
	int class_exists, is_backlogged;
	struct jobs_class *cl;

	now = read_machclk();
	cl = jif->jif_classes[i];
	class_exists = (cl != NULL);
	is_backlogged = (class_exists && !qempty(cl->cl_q));

	if (is_backlogged)
		return ((int64_t)delay_diff(now, tslist_first(cl->arv_tm)->timestamp));

	return 0; /* NOTREACHED */
}

/*
 * pick_dropped_rlc: returns the class index of the class to be
 * dropped for meeting the relative loss constraints.
 */
static int
pick_dropped_rlc(struct jobs_if *jif)
{
	int64_t mean;
	int64_t* loss_error;
	int i, active_classes;
	int class_exists, is_backlogged;
	int class_dropped;
	int64_t max_error;
	int64_t max_alc;
	struct mbuf* pkt;
	struct jobs_class *cl;
	u_int64_t len;

	loss_error = malloc(sizeof(int64_t)*(jif->jif_maxpri+1),
	    M_DEVBUF, M_WAITOK);

	if (loss_error == NULL)
		return -1;

	class_dropped = -1;
	max_error = 0;
	mean = 0;
	active_classes = 0;

	for (i = 0; i <= jif->jif_maxpri; i++) {
		cl = jif->jif_classes[i];
		class_exists = (cl != NULL);
		is_backlogged = (class_exists && !qempty(cl->cl_q));
		if (is_backlogged) {
			if (cl->concerned_rlc) {
				mean += cl->loss_prod_others
				    * cl->current_loss;
				active_classes++;
			}
		}
	}

	if (active_classes > 0)
		mean /= active_classes;

	if (active_classes == 0)
		class_dropped = JOBS_MAXPRI+1; /*
						* no classes are concerned
						* by RLCs (JOBS_MAXPRI+1
						* means "ignore RLC" here)
						*/
	else {
		for (i = 0; i <= jif->jif_maxpri; i++) {
			cl = jif->jif_classes[i];
			class_exists = (cl != NULL);
			is_backlogged = (class_exists
					 && !qempty(cl->cl_q));

			if ((is_backlogged)&&(cl->cl_rlc))
				loss_error[i]=cl->loss_prod_others
				    *cl->current_loss-mean;
			else
				loss_error[i] = ALTQ_INFINITY;
		}

		for (i = 0; i <= jif->jif_maxpri; i++) {
			cl = jif->jif_classes[i];
			class_exists = (cl != NULL);
			is_backlogged = (class_exists
					 && !qempty(cl->cl_q));
			if (is_backlogged && loss_error[i] <= max_error) {
				/*
				 * find out which class is the most
				 * below the mean.
				 * it's the one that needs to be dropped
				 * ties are broken in favor of the higher
				 * priority classes (i.e., if two classes
				 * present the same deviation, the lower
				 * priority class will get dropped).
				 */
				max_error = loss_error[i];
				class_dropped = i;
			}
		}

		if (class_dropped != -1) {
			cl = jif->jif_classes[class_dropped];
			pkt = qtail(cl->cl_q);
			if (pkt != NULL) {
				/*
				 * "safeguard" test (a packet SHOULD be
				 * in there)
				 */
				len = (u_int64_t)m_pktlen(pkt);
				/* access packet at the tail */
				if (cl->current_loss+(len << SCALE_LOSS)/cl->cl_arrival.bytes > cl->cl_alc) {
					/*
					 * the class to drop for meeting
					 * the RLC will defeat the ALC:
					 * ignore RLC.
					 */
					class_dropped = JOBS_MAXPRI+1;
				}
			} else
				class_dropped = JOBS_MAXPRI+1; /* NOTREACHED */
		} else
			class_dropped = JOBS_MAXPRI+1;
	}

	if (class_dropped == JOBS_MAXPRI+1) {
		max_alc = -((int64_t)1 << SCALE_LOSS);
		for (i = jif->jif_maxpri; i >= 0; i--) {
			cl = jif->jif_classes[i];
			class_exists = (cl != NULL);
			is_backlogged = (class_exists
					 && !qempty(cl->cl_q));
			if (is_backlogged) {
				if (cl->concerned_alc && cl->cl_alc - cl->current_loss > max_alc) {
					max_alc = cl->cl_alc-cl->current_loss; /* pick the class which is the furthest from its ALC */
					class_dropped = i;
				} else if (!cl->concerned_alc && ((int64_t) 1 << SCALE_LOSS)-cl->current_loss > max_alc) {
					max_alc = ((int64_t) 1 << SCALE_LOSS)-cl->current_loss;
					class_dropped = i;
				}
			}
		}
	}

	free(loss_error, M_DEVBUF);
	return (class_dropped);
}

/*
 * ALTQ binding/setup functions
 */
/*
 * jobs device interface
 */
int
jobsopen(dev_t dev, int flag, int fmt,
    struct lwp *l)
{
	if (machclk_freq == 0)
		init_machclk();

	if (machclk_freq == 0) {
		printf("jobs: no CPU clock available!\n");
		return ENXIO;
	}
	/* everything will be done when the queueing scheme is attached. */
	return 0;
}

int
jobsclose(dev_t dev, int flag, int fmt,
    struct lwp *l)
{
	struct jobs_if *jif;

	while ((jif = jif_list) != NULL) {
		/* destroy all */
		if (ALTQ_IS_ENABLED(jif->jif_ifq))
			altq_disable(jif->jif_ifq);

		int error = altq_detach(jif->jif_ifq);
		switch (error) {
		case 0:
		case ENXIO:	/* already disabled */
			break;
		default:
			return error;
		}
		jobs_detach(jif);
	}

	return 0;
}

int
jobsioctl(dev_t dev, ioctlcmd_t cmd, void *addr, int flag,
    struct lwp *l)
{
	struct jobs_if *jif;
	struct jobs_interface *ifacep;
	int	error = 0;

	/* check super-user privilege */
	switch (cmd) {
	case JOBS_GETSTATS:
		break;
	default:
		if ((error = kauth_authorize_network(l->l_cred,
		    KAUTH_NETWORK_ALTQ, KAUTH_REQ_NETWORK_ALTQ_JOBS, NULL,
		    NULL, NULL)) != 0)
			return (error);
		break;
	}

	switch (cmd) {

	case JOBS_IF_ATTACH:
		error = jobscmd_if_attach((struct jobs_attach *)addr);
		break;

	case JOBS_IF_DETACH:
		error = jobscmd_if_detach((struct jobs_interface *)addr);
		break;

	case JOBS_ENABLE:
	case JOBS_DISABLE:
	case JOBS_CLEAR:
		ifacep = (struct jobs_interface *)addr;
		if ((jif = altq_lookup(ifacep->jobs_ifname,
				       ALTQT_JOBS)) == NULL) {
			error = EBADF;
			break;
		}

		switch (cmd) {
		case JOBS_ENABLE:
			if (jif->jif_default == NULL) {
#if 1
				printf("jobs: no default class\n");
#endif
				error = EINVAL;
				break;
			}
			error = altq_enable(jif->jif_ifq);
			break;

		case JOBS_DISABLE:
			error = altq_disable(jif->jif_ifq);
			break;

		case JOBS_CLEAR:
			jobs_clear_interface(jif);
			break;
		}
		break;

		case JOBS_ADD_CLASS:
			error = jobscmd_add_class((struct jobs_add_class *)addr);
			break;

	case JOBS_DEL_CLASS:
		error = jobscmd_delete_class((struct jobs_delete_class *)addr);
		break;

	case JOBS_MOD_CLASS:
		error = jobscmd_modify_class((struct jobs_modify_class *)addr);
		break;

	case JOBS_ADD_FILTER:
		error = jobscmd_add_filter((struct jobs_add_filter *)addr);
		break;

	case JOBS_DEL_FILTER:
		error = jobscmd_delete_filter((struct jobs_delete_filter *)addr);
		break;

	case JOBS_GETSTATS:
		error = jobscmd_class_stats((struct jobs_class_stats *)addr);
		break;

	default:
		error = EINVAL;
		break;
	}
	return error;
}

static int
jobscmd_if_attach(struct jobs_attach *ap)
{
	struct jobs_if *jif;
	struct ifnet *ifp;
	int error;

	if ((ifp = ifunit(ap->iface.jobs_ifname)) == NULL)
		return ENXIO;
	if ((jif = jobs_attach(&ifp->if_snd, ap->bandwidth, ap->qlimit, ap->separate)) == NULL)
		return ENOMEM;

	/*
	 * set JOBS to this ifnet structure.
	 */
	if ((error = altq_attach(&ifp->if_snd, ALTQT_JOBS, jif,
				 jobs_enqueue, jobs_dequeue, jobs_request,
				 &jif->jif_classifier, acc_classify)) != 0)
		jobs_detach(jif);

	return error;
}

static int
jobscmd_if_detach(struct jobs_interface *ap)
{
	struct jobs_if *jif;
	int error;

	if ((jif = altq_lookup(ap->jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	if (ALTQ_IS_ENABLED(jif->jif_ifq))
		altq_disable(jif->jif_ifq);

	if ((error = altq_detach(jif->jif_ifq)))
		return error;

	jobs_detach(jif);
	return 0;
}

static int
jobscmd_add_class(struct jobs_add_class *ap)
{
	struct jobs_if *jif;
	struct jobs_class *cl;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	if (ap->pri < 0 || ap->pri >= JOBS_MAXPRI)
		return EINVAL;

	if ((cl = jobs_class_create(jif, ap->pri,
				    ap->cl_adc, ap->cl_rdc,
				    ap->cl_alc, ap->cl_rlc, ap-> cl_arc,
				    ap->flags)) == NULL)
		return ENOMEM;

	/* return a class handle to the user */
	ap->class_handle = clp_to_clh(cl);
	return 0;
}

static int
jobscmd_delete_class(struct jobs_delete_class *ap)
{
	struct jobs_if *jif;
	struct jobs_class *cl;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	if ((cl = clh_to_clp(jif, ap->class_handle)) == NULL)
		return EINVAL;

	return jobs_class_destroy(cl);
}

static int
jobscmd_modify_class(struct jobs_modify_class *ap)
{
	struct jobs_if *jif;
	struct jobs_class *cl;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	if (ap->pri < 0 || ap->pri >= JOBS_MAXPRI)
		return EINVAL;

	if ((cl = clh_to_clp(jif, ap->class_handle)) == NULL)
		return EINVAL;

	/*
	 * if priority is changed, move the class to the new priority
	 */
	if (jif->jif_classes[ap->pri] != cl) {
		if (jif->jif_classes[ap->pri] != NULL)
			return EEXIST;
		jif->jif_classes[cl->cl_pri] = NULL;
		jif->jif_classes[ap->pri] = cl;
		cl->cl_pri = ap->pri;
	}

	/* call jobs_class_create to change class parameters */
	if ((cl = jobs_class_create(jif, ap->pri,
				    ap->cl_adc, ap->cl_rdc,
				    ap->cl_alc, ap->cl_rlc, ap->cl_arc,
				    ap->flags)) == NULL)
		return ENOMEM;
	return 0;
}

static int
jobscmd_add_filter(struct jobs_add_filter *ap)
{
	struct jobs_if *jif;
	struct jobs_class *cl;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	if ((cl = clh_to_clp(jif, ap->class_handle)) == NULL)
		return EINVAL;

	return acc_add_filter(&jif->jif_classifier, &ap->filter,
			      cl, &ap->filter_handle);
}

static int
jobscmd_delete_filter(struct jobs_delete_filter *ap)
{
	struct jobs_if *jif;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	return acc_delete_filter(&jif->jif_classifier, ap->filter_handle);
}

static int
jobscmd_class_stats(struct jobs_class_stats *ap)
{
	struct jobs_if *jif;
	struct jobs_class *cl;
	struct class_stats stats, *usp;
	int pri, error;

	if ((jif = altq_lookup(ap->iface.jobs_ifname, ALTQT_JOBS)) == NULL)
		return EBADF;

	ap->maxpri = jif->jif_maxpri;

	/* then, read the next N classes */
	usp = ap->stats;
	for (pri = 0; pri <= jif->jif_maxpri; pri++) {
		cl = jif->jif_classes[pri];
		(void)memset(&stats, 0, sizeof(stats));
		if (cl != NULL)
			get_class_stats(&stats, cl);
		if ((error = copyout((void *)&stats, (void *)usp++,
				     sizeof(stats))) != 0)
			return error;
	}
	return 0;
}

static void
get_class_stats(struct class_stats *sp, struct jobs_class *cl)
{
	u_int64_t now;
	now = read_machclk();

	sp->class_handle = clp_to_clh(cl);
	sp->qlength = qlen(cl->cl_q);

	sp->period = cl->cl_period;
	sp->rin = cl->st_rin;
	sp->arrival = cl->st_arrival;
	sp->arrivalbusy = cl->cl_arrival;
	sp->rout = cl->st_rout;
	sp->dropcnt = cl->cl_dropcnt;

	/*  PKTCNTR_RESET(&cl->st_arrival);*/
	PKTCNTR_RESET(&cl->st_rin);
	PKTCNTR_RESET(&cl->st_rout);

	sp->totallength = cl->cl_jif->jif_ifq->ifq_len;
	sp->lastdel = ticks_to_secs(GRANULARITY*cl->cl_lastdel);
	sp->avgdel = cl->cl_avgdel;

	cl->cl_avgdel = 0;

	sp->busylength = ticks_to_secs(1000*delay_diff(now, cl->idletime));
	sp->adc_violations = cl->adc_violations;

	sp->wc_cycles_enqueue = cl->cl_jif->wc_cycles_enqueue;
	sp->wc_cycles_dequeue = cl->cl_jif->wc_cycles_dequeue;
	sp->bc_cycles_enqueue = cl->cl_jif->bc_cycles_enqueue;
	sp->bc_cycles_dequeue = cl->cl_jif->bc_cycles_dequeue;
	sp->avg_cycles_enqueue = cl->cl_jif->avg_cycles_enqueue;
	sp->avg_cycles_dequeue = cl->cl_jif->avg_cycles_dequeue;
	sp->avg_cycles2_enqueue = cl->cl_jif->avg_cycles2_enqueue;
	sp->avg_cycles2_dequeue = cl->cl_jif->avg_cycles2_dequeue;
	sp->total_enqueued = cl->cl_jif->total_enqueued;
	sp->total_dequeued = cl->cl_jif->total_dequeued;
}

/* convert a class handle to the corresponding class pointer */
static struct jobs_class *
clh_to_clp(struct jobs_if *jif, u_long chandle)
{
	struct jobs_class *cl;

	cl = (struct jobs_class *)chandle;
	if (chandle != ALIGN(cl)) {
#if 1
		printf("clh_to_cl: unaligned pointer %p\n", cl);
#endif
		return NULL;
	}

	if (cl == NULL || cl->cl_handle != chandle || cl->cl_jif != jif)
		return NULL;
	return cl;
}

/* convert a class pointer to the corresponding class handle */
static u_long
clp_to_clh(struct jobs_class *cl)
{
	return (cl->cl_handle);
}

#ifdef KLD_MODULE

static struct altqsw jobs_sw =
	{"jobs", jobsopen, jobsclose, jobsioctl};

ALTQ_MODULE(altq_jobs, ALTQT_JOBS, &jobs_sw);

#endif /* KLD_MODULE */

#endif /* ALTQ3_COMPAT */
#endif /* ALTQ_JOBS */
