/* $NetBSD: qop_hfsc.c,v 1.11 2022/05/24 20:50:21 andvar Exp $ */
/* $KAME: qop_hfsc.c,v 1.12 2005/01/05 04:53:47 itojun Exp $ */
/*
* Copyright (C) 1999-2000
* Sony Computer Science Laboratories, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY SONY CSL 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 SONY CSL 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.
*/
/*
* if the link-sharing service curve is different from
* the real-time service curve, we first create a class with the
* smaller service curve and then modify the other service curve.
*/
if (rm2 <= fm2) {
m1 = rm1; d = rd; m2 = rm2;
} else {
m1 = fm1; d = fd; m2 = fm2;
}
error = qcmd_hfsc_add_class(ifname, class_name, parent_name,
m1, d, m2, qlimit, flags);
if (error == 0 && (rm1 != fm1 || rd != fd || rm2 != fm2)) {
if (rm2 <= fm2) {
m1 = fm1; d = fd; m2 = fm2; type = HFSC_LINKSHARINGSC;
} else {
m1 = rm1; d = rd; m2 = rm2; type = HFSC_REALTIMESC;
}
error = qcmd_hfsc_modify_class(ifname, class_name,
m1, d, m2, type);
}
if (error == 0 && admission) {
/* this is a special class for rsvp */
struct ifinfo *ifinfo = ifname2ifinfo(ifname);
struct classinfo *clinfo = clname2clinfo(ifinfo, class_name);
if (ifinfo->resv_class != NULL) {
LOG(LOG_ERR, 0,
"more than one admission class specified: %s",
class_name);
return (0);
}
ifinfo->resv_class = clinfo;
}
/* set delete hook */
clinfo->delete_hook = qop_hfsc_delete_class_hook;
if (flags & HFCF_DEFAULTCLASS)
hfsc_ifinfo->default_class = clinfo;
if (parent == NULL) {
/*
* if this is a root class, reserve 20% of the real-time
* bandwidth for safety.
* many network cards are not able to saturate the wire,
* and if we allocate real-time traffic more than the
* maximum sending rate of the card, hfsc is no longer
* able to meet the delay bound requirements.
*/
hfsc_clinfo->rsc.m1 = hfsc_clinfo->rsc.m1 / 10 * 8;
hfsc_clinfo->rsc.m2 = hfsc_clinfo->rsc.m2 / 10 * 8;
}
if (rp != NULL)
*rp = clinfo;
return (0);
err_ret:
/* cancel admission control */
if (parent != NULL && !is_sc_null(sc)) {
gsc_sub_sc(&parent_clinfo->gen_rsc, sc);
gsc_sub_sc(&parent_clinfo->gen_fsc, sc);
}
if (hfsc_clinfo != NULL) {
free(hfsc_clinfo);
clinfo->private = NULL;
}
return (error);
}
/*
* this is called from qop_delete_class() before a class is destroyed
* for discipline specific cleanup.
*/
static int
qop_hfsc_delete_class_hook(struct classinfo *clinfo)
{
struct hfsc_classinfo *hfsc_clinfo, *parent_clinfo;
hfsc_clinfo = clinfo->private;
/* cancel admission control */
if (clinfo->parent != NULL) {
parent_clinfo = clinfo->parent->private;
/* save old service curves */
rsc = hfsc_clinfo->rsc;
fsc = hfsc_clinfo->fsc;
usc = hfsc_clinfo->usc;
/* admission control */
if (sctype & HFSC_REALTIMESC) {
/* if the class has usc, rsc should be smaller than usc */
if (!is_sc_null(&hfsc_clinfo->usc)) {
gsc_head_t tmp_gen_rsc =
LIST_HEAD_INITIALIZER(tmp_gen_rsc);
if (!is_gsc_under_sc(&hfsc_clinfo->gen_rsc, sc)) {
/* admission control failure */
return (QOPERR_ADMISSION);
}
gsc_sub_sc(&parent_clinfo->gen_rsc, &hfsc_clinfo->rsc);
gsc_add_sc(&parent_clinfo->gen_rsc, sc);
if (!is_gsc_under_sc(&parent_clinfo->gen_rsc,
&parent_clinfo->rsc)) {
/* admission control failure */
gsc_sub_sc(&parent_clinfo->gen_rsc, sc);
gsc_add_sc(&parent_clinfo->gen_rsc, &hfsc_clinfo->rsc);
return (QOPERR_ADMISSION_NOBW);
}
hfsc_clinfo->rsc = *sc;
}
if (sctype & HFSC_LINKSHARINGSC) {
if (!is_gsc_under_sc(&hfsc_clinfo->gen_fsc, sc)) {
/* admission control failure */
return (QOPERR_ADMISSION);
}
gsc_sub_sc(&parent_clinfo->gen_fsc, &hfsc_clinfo->fsc);
gsc_add_sc(&parent_clinfo->gen_fsc, sc);
if (!is_gsc_under_sc(&parent_clinfo->gen_fsc,
&parent_clinfo->fsc)) {
/* admission control failure */
gsc_sub_sc(&parent_clinfo->gen_fsc, sc);
gsc_add_sc(&parent_clinfo->gen_fsc, &hfsc_clinfo->fsc);
return (QOPERR_ADMISSION_NOBW);
}
hfsc_clinfo->fsc = *sc;
}
if (sctype & HFSC_UPPERLIMITSC) {
if (!is_sc_null(sc)) {
/* usc must be smaller than interface bandwidth */
struct classinfo *root_clinfo =
clname2clinfo(clinfo->ifinfo, "root");
if (root_clinfo != NULL) {
struct hfsc_classinfo *root_hfsc_clinfo =
root_clinfo->private;
if (!is_sc_null(&root_hfsc_clinfo->rsc)) {
gsc_head_t tmp_gen_usc =
LIST_HEAD_INITIALIZER(tmp_gen_usc);
gsc_add_sc(&tmp_gen_usc, sc);
if (!is_gsc_under_sc(&tmp_gen_usc,
&root_hfsc_clinfo->fsc)) {
/* illegal attempt to set
upper limit curve to be
greater than the interface
bandwidth */
gsc_destroy(&tmp_gen_usc);
return (QOPERR_ADMISSION);
}
gsc_destroy(&tmp_gen_usc);
}
}
/* if this class has rsc, check that usc >= rsc */
if (!is_sc_null(&hfsc_clinfo->rsc)) {
gsc_head_t tmp_gen_rsc =
LIST_HEAD_INITIALIZER(tmp_gen_rsc);
gsc_add_sc(&tmp_gen_rsc, &hfsc_clinfo->rsc);
if (!is_gsc_under_sc(&tmp_gen_rsc, sc)) {
/* illegal attempt to set upper limit
curve to be under the real-time
service curve */
gsc_destroy(&tmp_gen_rsc);
return (QOPERR_ADMISSION);
}
gsc_destroy(&tmp_gen_rsc);
}
}
hfsc_clinfo->usc = *sc;
}
/* modify failed!, restore the old service curves */
if (sctype & HFSC_REALTIMESC) {
gsc_sub_sc(&parent_clinfo->gen_rsc, sc);
gsc_add_sc(&parent_clinfo->gen_rsc, &rsc);
hfsc_clinfo->rsc = rsc;
}
if (sctype & HFSC_LINKSHARINGSC) {
gsc_sub_sc(&parent_clinfo->gen_fsc, sc);
gsc_add_sc(&parent_clinfo->gen_fsc, &fsc);
hfsc_clinfo->fsc = fsc;
}
if (sctype & HFSC_UPPERLIMITSC) {
hfsc_clinfo->usc = usc;
}
return (error);
}
/*
* sanity check at enabling hfsc:
* 1. there must one default class for an interface
* 2. the default class must be a leaf class
* 3. an internal class should not have filters
* (rule 2 and 3 are due to the fact that the hfsc link-sharing algorithm
* do not schedule internal classes.)
*/
static int
qop_hfsc_enable_hook(struct ifinfo *ifinfo)
{
struct hfsc_ifinfo *hfsc_ifinfo;
struct classinfo *clinfo;
hfsc_ifinfo = ifinfo->private;
if (hfsc_ifinfo->default_class == NULL) {
LOG(LOG_ERR, 0, "hfsc: no default class on interface %s!",
ifinfo->ifname);
return (QOPERR_CLASS);
} else if (hfsc_ifinfo->default_class->child != NULL) {
LOG(LOG_ERR, 0, "hfsc: default class on %s must be a leaf!",
ifinfo->ifname);
return (QOPERR_CLASS);
}
LIST_FOREACH(clinfo, &ifinfo->cllist, next) {
if (clinfo->child != NULL && !LIST_EMPTY(&clinfo->fltrlist)) {
LOG(LOG_ERR, 0,
"hfsc: internal class \"%s\" should not have a filter!",
clinfo->clname);
return (QOPERR_CLASS);
}
}
return (0);
}
static int
validate_sc(struct service_curve *sc)
{
/* the 1st segment of a concave curve must be zero */
if (sc->m1 < sc->m2 && sc->m1 != 0) {
LOG(LOG_ERR, 0, "m1 must be 0 for convex!");
return (-1);
}
if (sc->m1 > sc->m2 && sc->m2 == 0) {
LOG(LOG_ERR, 0, "m2 must be nonzero for concave!");
return (-1);
}
return (0);
}
/*
* admission control using generalized service curve
*/
/* add a new service curve to a generilized service curve */
static void
gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc)
{
if (is_sc_null(sc))
return;
if (sc->d != 0)
gsc_add_seg(gsc, 0, 0, (double)sc->d, (double)sc->m1);
gsc_add_seg(gsc, (double)sc->d, 0, HUGE_VAL, (double)sc->m2);
}
/* subtract a service curve from a generilized service curve */
static void
gsc_sub_sc(struct gen_sc *gsc, struct service_curve *sc)
{
if (is_sc_null(sc))
return;
if (sc->d != 0)
gsc_sub_seg(gsc, 0, 0, (double)sc->d, (double)sc->m1);
gsc_sub_seg(gsc, (double)sc->d, 0, HUGE_VAL, (double)sc->m2);
}
/*
* check whether all points of a generalized service curve have
* their y-coordinates no larger than a given two-piece linear
* service curve.
*/
static int
is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc)
{
struct segment *s, *last, *end;
double y;
if (is_sc_null(sc)) {
if (LIST_EMPTY(gsc))
return (1);
LIST_FOREACH(s, gsc, _next) {
if (s->m != 0)
return (0);
}
return (1);
}
/*
* gsc has a dummy entry at the end with x = HUGE_VAL.
* loop through up to this dummy entry.
*/
end = gsc_getentry(gsc, HUGE_VAL);
if (end == NULL)
return (1);
last = NULL;
for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) {
if (s->y > sc_x2y(sc, s->x))
return (0);
last = s;
}
/* last now holds the real last segment */
if (last == NULL)
return (1);
if (last->m > sc->m2)
return (0);
if (last->x < sc->d && last->m > sc->m1) {
y = last->y + (sc->d - last->x) * last->m;
if (y > sc_x2y(sc, sc->d))
return (0);
}
return (1);
}
/*
* return a segment entry starting at x.
* if gsc has no entry starting at x, a new entry is created at x.
*/
static struct segment *
gsc_getentry(struct gen_sc *gsc, double x)
{
struct segment *new, *prev, *s;
prev = NULL;
LIST_FOREACH(s, gsc, _next) {
if (s->x == x)
return (s); /* matching entry found */
else if (s->x < x)
prev = s;
else
break;
}
/* we have to create a new entry */
if ((new = calloc(1, sizeof(struct segment))) == NULL)
return (NULL);
new->x = x;
if (x == HUGE_VAL || s == NULL)
new->d = 0;
else if (s->x == HUGE_VAL)
new->d = HUGE_VAL;
else
new->d = s->x - x;
if (prev == NULL) {
/* insert the new entry at the head of the list */
new->y = 0;
new->m = 0;
LIST_INSERT_HEAD(gsc, new, _next);
} else {
/*
* the start point intersects with the segment pointed by
* prev. divide prev into 2 segments
*/
if (x == HUGE_VAL) {
prev->d = HUGE_VAL;
if (prev->m == 0)
new->y = prev->y;
else
new->y = HUGE_VAL;
} else {
prev->d = x - prev->x;
new->y = prev->d * prev->m + prev->y;
}
new->m = prev->m;
LIST_INSERT_AFTER(prev, new, _next);
}
return (new);
}
/* add a segment to a generalized service curve */
static int
gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m)
{
struct segment *start, *end, *s;
double x2;
if (d == HUGE_VAL)
x2 = HUGE_VAL;
else
x2 = x + d;
start = gsc_getentry(gsc, x);
end = gsc_getentry(gsc, x2);
if (start == NULL || end == NULL)
return (-1);
for (s = start; s != end; s = LIST_NEXT(s, _next)) {
s->m += m;
s->y += y + (s->x - x) * m;
}
end = gsc_getentry(gsc, HUGE_VAL);
for (; s != end; s = LIST_NEXT(s, _next)) {
s->y += m * d;
}
return (0);
}
/* subtract a segment from a generalized service curve */
static int
gsc_sub_seg(struct gen_sc *gsc, double x, double y, double d, double m)
{
if (gsc_add_seg(gsc, x, y, d, -m) < 0)
return (-1);
gsc_compress(gsc);
return (0);
}
/*
* collapse adjacent segments with the same slope
*/
static void
gsc_compress(struct gen_sc *gsc)
{
struct segment *s, *next;
again:
LIST_FOREACH(s, gsc, _next) {
if ((next = LIST_NEXT(s, _next)) == NULL) {
if (LIST_FIRST(gsc) == s && s->m == 0) {
/*
* if this is the only entry and its
* slope is 0, it's a remaining dummy
* entry. we can discard it.
*/
LIST_REMOVE(s, _next);
free(s);
}
break;
}
if (s->x == next->x) {
/* discard this entry */
LIST_REMOVE(s, _next);
free(s);
goto again;
} else if (s->m == next->m) {
/* join the two entries */
if (s->d != HUGE_VAL && next->d != HUGE_VAL)
s->d += next->d;
LIST_REMOVE(next, _next);
free(next);
goto again;
}
}
}
/* get y-projection of a service curve */
static double
sc_x2y(struct service_curve *sc, double x)
{
double y;
if (x <= (double)sc->d)
/* y belongs to the 1st segment */
y = x * (double)sc->m1;
else
/* y belongs to the 2nd segment */
y = (double)sc->d * (double)sc->m1
+ (x - (double)sc->d) * (double)sc->m2;
return (y);
}
/*
* system call interfaces for qdisc_ops
*/
static int
hfsc_attach(struct ifinfo *ifinfo)
{
struct hfsc_attach attach;