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 <netlink/netlink.h>
+#include <netlink/route/tc.h>
+
+#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 <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/route/qdisc/hfsc.h>
+
+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 <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/cache.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/class.h>
+#include <netlink/route/link.h>
+#include <netlink/route/qdisc/hfsc.h>
+
+#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);
+}
