diff --git a/include/Makefile.am b/include/Makefile.am index 3f96690..79206fd 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -54,6 +54,7 @@ nobase_libnlinclude_HEADERS = \ netlink/route/qdisc/red.h \ netlink/route/qdisc/sfq.h \ netlink/route/qdisc/tbf.h \ + netlink/route/qdisc/hfsc.h \ netlink/route/qdisc/fq_codel.h \ netlink/route/qdisc/plug.h \ netlink/route/addr.h \ diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h index d6d056d..ba21a7f 100644 --- a/include/netlink-private/types.h +++ b/include/netlink-private/types.h @@ -651,6 +651,27 @@ struct rtnl_netem struct rtnl_netem_dist qnm_dist; }; +struct rtnl_hfsc_qdisc +{ + uint32_t qsc_defcls; + uint32_t qsc_mask; +}; + +struct rtnl_curve +{ + uint32_t sc_m1; + uint32_t sc_d; + uint32_t sc_m2; +}; + +struct rtnl_hfsc_class +{ + struct rtnl_curve csc_rt_sc; + struct rtnl_curve csc_fair_sc; + struct rtnl_curve csc_upper_sc; + uint32_t csc_mask; +}; + struct rtnl_htb_qdisc { uint32_t qh_rate2quantum; diff --git a/include/netlink/route/qdisc/hfsc.h b/include/netlink/route/qdisc/hfsc.h new file mode 100644 index 0000000..d47b29d --- /dev/null +++ b/include/netlink/route/qdisc/hfsc.h @@ -0,0 +1,46 @@ +/* + * netlink/route/sch/hfsc.h HFSC Qdisc + */ + +#ifndef NETLINK_HFSC_H_ +#define NETLINK_HFSC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct hfsc_spec { + unsigned int rate; + unsigned int delay_max; + unsigned int work_max; +}; + +extern uint32_t rtnl_hfsc_get_defcls(struct rtnl_qdisc *); +extern int rtnl_hfsc_set_defcls(struct rtnl_qdisc *, uint32_t); + +/* m1-d-m2: Direct Method of Specifying service curve slopes */ +extern void rtnl_hfsc_get_realtime_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_realtime_sc(struct rtnl_class *, + struct tc_service_curve *); +extern void rtnl_hfsc_get_fair_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_fair_sc(struct rtnl_class *, + struct tc_service_curve *); +extern void rtnl_hfsc_get_upperlimit_sc(struct rtnl_class *, + struct tc_service_curve *); +extern int rtnl_hfsc_set_upperlimit_sc(struct rtnl_class *, + struct tc_service_curve *); + +/* umax-dmax-rate Method of specifying service curve translations */ +extern int rtnl_hfsc_spec_to_sc(struct hfsc_spec *, + struct tc_service_curve *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/Makefile.am b/lib/Makefile.am index 1dce098..e715ab7 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -76,6 +76,7 @@ libnl_route_3_la_SOURCES = \ route/qdisc/prio.c route/qdisc/red.c route/qdisc/sfq.c \ route/qdisc/tbf.c route/qdisc/plug.c \ route/qdisc/fq_codel.c \ + route/qdisc/hfsc.c \ \ fib_lookup/lookup.c fib_lookup/request.c \ \ @@ -102,6 +103,7 @@ EXTRA_DIST = \ if ENABLE_CLI nobase_pkglib_LTLIBRARIES = \ cli/qdisc/htb.la \ + cli/qdisc/hfsc.la \ cli/qdisc/blackhole.la \ cli/qdisc/pfifo.la \ cli/qdisc/plug.la \ @@ -110,6 +112,7 @@ nobase_pkglib_LTLIBRARIES = \ cli/cls/cgroup.la cli_qdisc_htb_la_LDFLAGS = -module -avoid-version +cli_qdisc_hfsc_la_LDFLAGS = -module -avoid-version cli_qdisc_blackhole_la_LDFLAGS = -module -avoid-version cli_qdisc_pfifo_la_LDFLAGS = -module -avoid-version cli_qdisc_plug_la_LDFLAGS = -module -avoid-version diff --git a/lib/cli/qdisc/hfsc.c b/lib/cli/qdisc/hfsc.c new file mode 100644 index 0000000..1c0f713 --- /dev/null +++ b/lib/cli/qdisc/hfsc.c @@ -0,0 +1,221 @@ +/* + * lib/cli/qdisc/hfsc.c HFSC module for CLI lib + */ +/* + ************************************************************************** + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all copies. + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + ************************************************************************** + */ + +#include +#include +#include + +static void print_qdisc_usage(void) +{ + printf( +"Usage: nl-qdisc-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" --help Show this help text.\n" +" --default=ID Default class for unclassified traffic.\n" +"\n" +"EXAMPLE" +" # Create hfsc root qdisc 1: and direct unclassified traffic to class 1:3\n" +" nl-qdisc-add --dev=eth1 --parent=root --handle=1: hfsc --default=3\n"); +} + +static void hfsc_parse_qdisc_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_qdisc *qdisc = (struct rtnl_qdisc *) tc; + + for (;;) { + int c, optidx = 0; + enum { + ARG_DEFAULT = 257, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "default", 1, 0, ARG_DEFAULT }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hv", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_qdisc_usage(); + return; + case ARG_DEFAULT: + rtnl_hfsc_set_defcls(qdisc, nl_cli_parse_u32(optarg)); + break; + } + } +} + +#if 0 +static void print_class_usage(void) +{ + printf( +"Usage: nl-class-add [...] hfsc [OPTIONS]...\n" +"\n" +"OPTIONS\n" +" Usage: ... hfsc [ [ --rt \"SC\" ] [ --ls \"SC\" ] | [--sc \"SC\" ] ] [--ul\"SC\" ]\n" +" --help Show this help text.\n" +" --realtime=\"SC\" Realtime service curve. Cannot be used with sc.\n" +" --linkshare=\"SC\" Link sharing service curve. Cannot be used with ls\n" +" --servicecurve=\"SC\" Combined service curve; Cannot be used with rt or ls.\n" +" --upperlimit=\"SC\" Upper limit service curve.\n" +" Slope Format\n" +" SC := [ [ m1 BPS ] [ d SEC ] m2 BPS\n" +" m1 : slope of first segment\n" +" d : x-coordinate of intersection\n" +" m2 : slope of second segment\n" +" Alternative Format:\n" +" SC := [ [ umax BYTE ] dmax SEC ] rate BPS\n" +" umax : maximum unit of work\n" +" dmax : maximum Delay\n" +" rate : rate\n" +"\n" +" Note: Service curve definitions must be in quotes on the command line.\n" +" Note: Must provide at least one service curve.\n" +" Note: If Upper limit is provided, linkshare or combined service curve must be given.\n" +"EXAMPLE" +" # Attach class 1:1 to hfsc qdisc 1: and rate limit it to 20mbit\n" +" nl-class-add --dev=eth1 --parent=1: --classid=1:1 hfsc --sc=\"rate 20mbit\"\n"); +} + + +static int hfsc_get_sc_slope(char *optarg, struct service_curve *sc_out) +{ + return 0; +} + +static int hfsc_get_sc_alt(char *optarg, struct service_curve *sc_out) +{ + return 0; +} + +static int hfsc_get_sc(char *optarg, struct service_curve *sc_out) +{ + memset(sc_out, 0, sizeof(*sc_out)); + + if ((hfsc_get_sc_slope(optarg, sc) < 0) && + (hfsc_get_sc_alt(optarg, sc) < 0)) + return -EINVAL; + + if ((sc->m1 == 0) && (sc->m2 == 0)) { + return -ENOENT; + } + +} + +static void hfsc_parse_class_argv(struct rtnl_tc *tc, int argc, char **argv) +{ + struct rtnl_class *class = (struct rtnl_class *) tc; + struct tc_service_curve sc; + long rate; + + for (;;) { + int c, optidx = 0; + enum { + ARG_RT = 257, + ARG_LS = 258, + ARG_SC = 259, + ARG_UL = 260, + }; + static struct option long_opts[] = { + { "help", 0, 0, 'h' }, + { "realtime", 1, 0, ARG_RT }, + { "linkshare", 1, 0, ARG_LS }, + { "servicecurve", 1, 0, ARG_SC }, + { "upperlimit", 1, 0, ARG_UL }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "h", long_opts, &optidx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_class_usage(); + return; + + case ARG_RT: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse htb rate " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_rate(class, rate); + break; + + case ARG_LS: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse htb ceil rate " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_ceil(class, rate); + break; + + case ARG_SC: + rtnl_htb_set_prio(class, nl_cli_parse_u32(optarg)); + break; + + case ARG_UL: + rate = nl_size2int(optarg); + if (rate < 0) { + nl_cli_fatal(rate, "Unable to parse quantum " + "\"%s\": Invalid format.", optarg); + } + + rtnl_htb_set_quantum(class, rate); + break; + } +} + +#endif + +static struct nl_cli_tc_module hfsc_qdisc_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_QDISC, + .tm_parse_argv = hfsc_parse_qdisc_argv, +}; + +#if 0 +static struct nl_cli_tc_module hfsc_class_module = +{ + .tm_name = "hfsc", + .tm_type = RTNL_TC_TYPE_CLASS, + .tm_parse_argv = hfsc_parse_class_argv, +}; +#endif + +static void __init hfsc_init(void) +{ + nl_cli_tc_register(&hfsc_qdisc_module); +// nl_cli_tc_register(&hfsc_class_module); +} + +static void __exit hfsc_exit(void) +{ +// nl_cli_tc_unregister(&hfsc_class_module); + nl_cli_tc_unregister(&hfsc_qdisc_module); +} diff --git a/lib/route/qdisc/hfsc.c b/lib/route/qdisc/hfsc.c new file mode 100644 index 0000000..396c687 --- /dev/null +++ b/lib/route/qdisc/hfsc.c @@ -0,0 +1,404 @@ +/* + * lib/route/qdisc/hfsc.c HFSC Qdisc + */ + +/** + * @ingroup qdisc + * @ingroup class + * @defgroup qdisc_hfsc Hierachical Fair Service Curve (HFSC) + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USEC_PER_SEC 1000000 + +/** @cond SKIP */ +#define SCH_HFSC_HAS_DEFCLS 0x02 + +#define SCH_HFSC_HAS_RT_SC 0x001 +#define SCH_HFSC_HAS_FAIR_SC 0x002 +#define SCH_HFSC_HAS_UPPER_SC 0x004 +/** @endcond */ + +static struct nla_policy hfsc_policy[TCA_HFSC_MAX + 1] = { + [TCA_HFSC_RSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_FSC] = { .minlen = sizeof(struct tc_service_curve) }, + [TCA_HFSC_USC] = { .minlen = sizeof(struct tc_service_curve) }, +}; + +static int hfsc_qdisc_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt *opt; + + if (tc->tc_opts->d_size < sizeof(struct tc_hfsc_qopt)) + return -NLE_INVAL; + + opt = (struct tc_hfsc_qopt *) tc->tc_opts->d_data; + hfsc->qsc_defcls = opt->defcls; + hfsc->qsc_mask = SCH_HFSC_HAS_DEFCLS; + + return 0; +} + +static int hfsc_class_msg_parser(struct rtnl_tc *tc, void *data) +{ + struct nlattr *tb[TCA_HFSC_MAX + 1]; + struct rtnl_hfsc_class *hfsc = data; + int err; + + if ((err = tca_parse(tb, TCA_HFSC_MAX, tc, hfsc_policy)) < 0) + return err; + + hfsc->csc_mask = 0; + if (tb[TCA_HFSC_RSC]) { + nla_memcpy(&hfsc->csc_rt_sc, tb[TCA_HFSC_RSC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_RT_SC; + } + + if (tb[TCA_HFSC_FSC]) { + nla_memcpy(&hfsc->csc_fair_sc, tb[TCA_HFSC_FSC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_FAIR_SC; + } + + if (tb[TCA_HFSC_USC]) { + nla_memcpy(&hfsc->csc_upper_sc, tb[TCA_HFSC_USC], + sizeof(struct tc_service_curve)); + hfsc->csc_mask |= SCH_HFSC_HAS_UPPER_SC; + } + + return 0; +} + +static void hfsc_qdisc_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + + if (!hfsc) + return; + + if (hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS) { + char buf[64]; + nl_dump(p, " default-class %s", + rtnl_tc_handle2str(hfsc->qsc_defcls, buf, sizeof(buf))); + } +} + + +static void hfsc_print_sc(struct nl_dump_params *p, char *name, + struct rtnl_curve *sc) +{ + double val; + char *unit; + + nl_dump(p, "%s ", name); + val = nl_cancel_down_bits(sc->sc_m1, &unit); + nl_dump(p, " m1 %.2f%s/s", val, unit); + + val = nl_cancel_down_us(sc->sc_d, &unit); + nl_dump(p, " d %.2f%s", val, unit); + + val = nl_cancel_down_bits(sc->sc_m2, &unit); + nl_dump(p, " m2 %.2f%s/s", val, unit); +} + + +static void hfsc_class_dump_line(struct rtnl_tc *tc, void *data, + struct nl_dump_params *p) +{ + struct rtnl_hfsc_class *hfsc = data; + + if (!hfsc) + return; + + if ((hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) && + (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) && + (memcmp(&hfsc->csc_rt_sc, + &hfsc->csc_fair_sc, + sizeof(hfsc->csc_rt_sc)) == 0)) { + hfsc_print_sc(p, "sc", &hfsc->csc_rt_sc); + } else { + if (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) + hfsc_print_sc(p, "rt", &hfsc->csc_rt_sc); + if (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) + hfsc_print_sc(p, "ls", &hfsc->csc_fair_sc); + } + + if (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC) + hfsc_print_sc(p, "ul", &hfsc->csc_upper_sc); +} + + +static int hfsc_qdisc_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_qdisc *hfsc = data; + struct tc_hfsc_qopt opts = {0}; + + if (!hfsc || !(hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS)) + return -NLE_INVAL; + + opts.defcls = hfsc->qsc_defcls; + + return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD); +} + +/* FIXME: Verify we have at least one curve, and if upper, must have fair */ +static int hfsc_class_msg_fill(struct rtnl_tc *tc, void *data, + struct nl_msg *msg) +{ + struct rtnl_hfsc_class *hfsc = data; + struct tc_service_curve sc; + + if (!hfsc) + BUG(); + + if (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC) { + memcpy(&sc, &hfsc->csc_rt_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_RSC, sizeof(sc), &sc); + + if (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC) { + memcpy(&sc, &hfsc->csc_fair_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_FSC, sizeof(sc), &sc); + + if (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC) { + memcpy(&sc, &hfsc->csc_upper_sc, sizeof(sc)); + } else { + memset(&sc, 0, sizeof(sc)); + } + NLA_PUT(msg, TCA_HFSC_USC, sizeof(sc), &sc); + + return 0; + +nla_put_failure: + return -NLE_MSGSIZE; +} + +static struct rtnl_tc_ops hfsc_qdisc_ops; +static struct rtnl_tc_ops hfsc_class_ops; + +static struct rtnl_hfsc_qdisc *hfsc_qdisc_data(struct rtnl_qdisc *qdisc) +{ + return rtnl_tc_data_check(TC_CAST(qdisc), &hfsc_qdisc_ops); +} + +static struct rtnl_hfsc_class *hfsc_class_data(struct rtnl_class *class) +{ + return rtnl_tc_data_check(TC_CAST(class), &hfsc_class_ops); +} + +/** + * @name Attribute Modifications + * @{ + */ + +/** + * Return default class of HFSC qdisc + * @arg qdisc hfsc qdisc object + * + * Returns the classid of the class where all unclassified traffic + * goes to. + * + * @return classid or TC_H_UNSPEC if unspecified. + */ +uint32_t rtnl_hfsc_get_defcls(struct rtnl_qdisc *qdisc) +{ + struct rtnl_hfsc_qdisc *hfsc; + + if ((hfsc = hfsc_qdisc_data(qdisc)) && + hfsc->qsc_mask & SCH_HFSC_HAS_DEFCLS) + return hfsc->qsc_defcls; + + return TC_H_UNSPEC; +} + +/** + * Set default class of the hfsc qdisc to the specified value + * @arg qdisc qdisc to change + * @arg defcls new default class + */ +int rtnl_hfsc_set_defcls(struct rtnl_qdisc *qdisc, uint32_t defcls) +{ + struct rtnl_hfsc_qdisc *hfsc; + + if (!(hfsc = hfsc_qdisc_data(qdisc))) + return -NLE_OPNOTSUPP; + + hfsc->qsc_defcls = defcls; + hfsc->qsc_mask |= SCH_HFSC_HAS_DEFCLS; + + return 0; +} + +/* m1-d-m2: Direct Method of Specifying service curve slopes */ +void rtnl_hfsc_get_realtime_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_RT_SC)) { + memcpy(curve_out, &hfsc->csc_rt_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_realtime_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_rt_sc, curve_in, sizeof(hfsc->csc_rt_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_RT_SC; + + return 0; +} + +void rtnl_hfsc_get_fair_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_FAIR_SC)) { + memcpy(curve_out, &hfsc->csc_fair_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_fair_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_fair_sc, curve_in, sizeof(hfsc->csc_fair_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_FAIR_SC; + + return 0; +} + +void rtnl_hfsc_get_upperlimit_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_out) +{ + struct rtnl_hfsc_class *hfsc; + + if ((hfsc = hfsc_class_data(cls)) && + (hfsc->csc_mask & SCH_HFSC_HAS_UPPER_SC)) { + memcpy(curve_out, &hfsc->csc_upper_sc, sizeof(*curve_out)); + } else { + memset(curve_out, 0, sizeof(*curve_out)); + } +} + + +int rtnl_hfsc_set_upperlimit_sc(struct rtnl_class *cls, + struct tc_service_curve *curve_in) +{ + struct rtnl_hfsc_class *hfsc; + + if (!(hfsc = hfsc_class_data(cls))) + return -NLE_OPNOTSUPP; + + memcpy(&hfsc->csc_upper_sc, curve_in, sizeof(hfsc->csc_upper_sc)); + hfsc->csc_mask |= SCH_HFSC_HAS_UPPER_SC; + + return 0; +} + +/* umax-dmax-rate Method of specifying service curve translations */ +int rtnl_hfsc_spec_to_sc(struct hfsc_spec *spec_in, + struct tc_service_curve *curve_out) +{ + unsigned int umax, dmax, rate; + + umax = spec_in->work_max; + dmax = spec_in->delay_max; + rate = spec_in->rate; + + if (umax != 0 && dmax == 0) { + return -EINVAL; + } + + if (rate == 0) { + curve_out->m1 = 0; + curve_out->d = 0; + curve_out->m2 = 0; + return 0; + } + + /* concave: slope of first segment is umax/dmax, intersect at dmax */ + if ((dmax != 0) && (ceil(1.0 * umax * USEC_PER_SEC / dmax) > rate)) { + curve_out->m1 = ceil(1.0 * umax * USEC_PER_SEC / dmax); + curve_out->d = dmax; + curve_out->m2 = rate; + } else { + /* convex: slope of first segment = 0, intersect (dmax - umax / rate */ + /* FIXME: is that calc correct? Maybe it's (dmax - umax) / rate? */ + curve_out->m1 = 0; + curve_out->d = ceil(dmax - umax * USEC_PER_SEC / rate); + curve_out->m2 = rate; + } + + return 0; +} + +/** @} */ + +static struct rtnl_tc_ops hfsc_qdisc_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_QDISC, + .to_size = sizeof(struct rtnl_hfsc_qdisc), + .to_msg_parser = hfsc_qdisc_msg_parser, + .to_dump[NL_DUMP_LINE] = hfsc_qdisc_dump_line, + .to_msg_fill = hfsc_qdisc_msg_fill, +}; + +static struct rtnl_tc_ops hfsc_class_ops = { + .to_kind = "hfsc", + .to_type = RTNL_TC_TYPE_CLASS, + .to_size = sizeof(struct rtnl_hfsc_class), + .to_msg_parser = hfsc_class_msg_parser, + .to_dump[NL_DUMP_LINE] = hfsc_class_dump_line, + .to_msg_fill = hfsc_class_msg_fill, +}; + +static void __init hfsc_init(void) +{ + rtnl_tc_register(&hfsc_qdisc_ops); + rtnl_tc_register(&hfsc_class_ops); +} + +static void __exit hfsc_exit(void) +{ + rtnl_tc_unregister(&hfsc_qdisc_ops); + rtnl_tc_unregister(&hfsc_class_ops); +}