+htb_query_class__(const struct netdev *netdev, unsigned int handle,
+ unsigned int parent, struct htb_class *options,
+ struct netdev_queue_stats *stats)
+{
+ struct ofpbuf *reply;
+ int error;
+
+ error = tc_query_class(netdev, handle, parent, &reply);
+ if (!error) {
+ error = htb_parse_tcmsg__(reply, NULL, options, stats);
+ ofpbuf_delete(reply);
+ }
+ return error;
+}
+
+static int
+htb_tc_install(struct netdev *netdev, const struct smap *details)
+{
+ int error;
+
+ error = htb_setup_qdisc__(netdev);
+ if (!error) {
+ struct htb_class hc;
+
+ htb_parse_qdisc_details__(netdev, details, &hc);
+ error = htb_setup_class__(netdev, tc_make_handle(1, 0xfffe),
+ tc_make_handle(1, 0), &hc);
+ if (!error) {
+ htb_install__(netdev, hc.max_rate);
+ }
+ }
+ return error;
+}
+
+static struct htb_class *
+htb_class_cast__(const struct tc_queue *queue)
+{
+ return CONTAINER_OF(queue, struct htb_class, tc_queue);
+}
+
+static void
+htb_update_queue__(struct netdev *netdev, unsigned int queue_id,
+ const struct htb_class *hc)
+{
+ struct htb *htb = htb_get__(netdev);
+ size_t hash = hash_int(queue_id, 0);
+ struct tc_queue *queue;
+ struct htb_class *hcp;
+
+ queue = tc_find_queue__(netdev, queue_id, hash);
+ if (queue) {
+ hcp = htb_class_cast__(queue);
+ } else {
+ hcp = xmalloc(sizeof *hcp);
+ queue = &hcp->tc_queue;
+ queue->queue_id = queue_id;
+ hmap_insert(&htb->tc.queues, &queue->hmap_node, hash);
+ }
+
+ hcp->min_rate = hc->min_rate;
+ hcp->max_rate = hc->max_rate;
+ hcp->burst = hc->burst;
+ hcp->priority = hc->priority;
+}
+
+static int
+htb_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+ struct ofpbuf msg;
+ struct nl_dump dump;
+ struct htb_class hc;
+
+ /* Get qdisc options. */
+ hc.max_rate = 0;
+ htb_query_class__(netdev, tc_make_handle(1, 0xfffe), 0, &hc, NULL);
+ htb_install__(netdev, hc.max_rate);
+
+ /* Get queues. */
+ if (!start_queue_dump(netdev, &dump)) {
+ return ENODEV;
+ }
+ while (nl_dump_next(&dump, &msg)) {
+ unsigned int queue_id;
+
+ if (!htb_parse_tcmsg__(&msg, &queue_id, &hc, NULL)) {
+ htb_update_queue__(netdev, queue_id, &hc);
+ }
+ }
+ nl_dump_done(&dump);
+
+ return 0;
+}
+
+static void
+htb_tc_destroy(struct tc *tc)
+{
+ struct htb *htb = CONTAINER_OF(tc, struct htb, tc);
+ struct htb_class *hc, *next;
+
+ HMAP_FOR_EACH_SAFE (hc, next, tc_queue.hmap_node, &htb->tc.queues) {
+ hmap_remove(&htb->tc.queues, &hc->tc_queue.hmap_node);
+ free(hc);
+ }
+ tc_destroy(tc);
+ free(htb);
+}
+
+static int
+htb_qdisc_get(const struct netdev *netdev, struct smap *details)
+{
+ const struct htb *htb = htb_get__(netdev);
+ smap_add_format(details, "max-rate", "%llu", 8ULL * htb->max_rate);
+ return 0;
+}
+
+static int
+htb_qdisc_set(struct netdev *netdev, const struct smap *details)
+{
+ struct htb_class hc;
+ int error;
+
+ htb_parse_qdisc_details__(netdev, details, &hc);
+ error = htb_setup_class__(netdev, tc_make_handle(1, 0xfffe),
+ tc_make_handle(1, 0), &hc);
+ if (!error) {
+ htb_get__(netdev)->max_rate = hc.max_rate;
+ }
+ return error;
+}
+
+static int
+htb_class_get(const struct netdev *netdev OVS_UNUSED,
+ const struct tc_queue *queue, struct smap *details)
+{
+ const struct htb_class *hc = htb_class_cast__(queue);
+
+ smap_add_format(details, "min-rate", "%llu", 8ULL * hc->min_rate);
+ if (hc->min_rate != hc->max_rate) {
+ smap_add_format(details, "max-rate", "%llu", 8ULL * hc->max_rate);
+ }
+ smap_add_format(details, "burst", "%llu", 8ULL * hc->burst);
+ if (hc->priority) {
+ smap_add_format(details, "priority", "%u", hc->priority);
+ }
+ return 0;
+}
+
+static int
+htb_class_set(struct netdev *netdev, unsigned int queue_id,
+ const struct smap *details)
+{
+ struct htb_class hc;
+ int error;
+
+ error = htb_parse_class_details__(netdev, details, &hc);
+ if (error) {
+ return error;
+ }
+
+ error = htb_setup_class__(netdev, tc_make_handle(1, queue_id + 1),
+ tc_make_handle(1, 0xfffe), &hc);
+ if (error) {
+ return error;
+ }
+
+ htb_update_queue__(netdev, queue_id, &hc);
+ return 0;
+}
+
+static int
+htb_class_delete(struct netdev *netdev, struct tc_queue *queue)
+{
+ struct htb_class *hc = htb_class_cast__(queue);
+ struct htb *htb = htb_get__(netdev);
+ int error;
+
+ error = tc_delete_class(netdev, tc_make_handle(1, queue->queue_id + 1));
+ if (!error) {
+ hmap_remove(&htb->tc.queues, &hc->tc_queue.hmap_node);
+ free(hc);
+ }
+ return error;
+}
+
+static int
+htb_class_get_stats(const struct netdev *netdev, const struct tc_queue *queue,
+ struct netdev_queue_stats *stats)
+{
+ return htb_query_class__(netdev, tc_make_handle(1, queue->queue_id + 1),
+ tc_make_handle(1, 0xfffe), NULL, stats);
+}
+
+static int
+htb_class_dump_stats(const struct netdev *netdev OVS_UNUSED,
+ const struct ofpbuf *nlmsg,
+ netdev_dump_queue_stats_cb *cb, void *aux)
+{
+ struct netdev_queue_stats stats;
+ unsigned int handle, major, minor;
+ int error;
+
+ error = tc_parse_class(nlmsg, &handle, NULL, &stats);
+ if (error) {
+ return error;
+ }
+
+ major = tc_get_major(handle);
+ minor = tc_get_minor(handle);
+ if (major == 1 && minor > 0 && minor <= HTB_N_QUEUES) {
+ (*cb)(minor - 1, &stats, aux);
+ }
+ return 0;
+}
+
+static const struct tc_ops tc_ops_htb = {
+ "htb", /* linux_name */
+ "linux-htb", /* ovs_name */
+ HTB_N_QUEUES, /* n_queues */
+ htb_tc_install,
+ htb_tc_load,
+ htb_tc_destroy,
+ htb_qdisc_get,
+ htb_qdisc_set,
+ htb_class_get,
+ htb_class_set,
+ htb_class_delete,
+ htb_class_get_stats,
+ htb_class_dump_stats
+};
+\f
+/* "linux-hfsc" traffic control class. */
+
+#define HFSC_N_QUEUES 0xf000
+
+struct hfsc {
+ struct tc tc;
+ uint32_t max_rate;
+};
+
+struct hfsc_class {
+ struct tc_queue tc_queue;
+ uint32_t min_rate;
+ uint32_t max_rate;
+};
+
+static struct hfsc *
+hfsc_get__(const struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev;
+ netdev_dev = netdev_dev_linux_cast(netdev_get_dev(netdev));
+ return CONTAINER_OF(netdev_dev->tc, struct hfsc, tc);
+}
+
+static struct hfsc_class *
+hfsc_class_cast__(const struct tc_queue *queue)
+{
+ return CONTAINER_OF(queue, struct hfsc_class, tc_queue);
+}
+
+static void
+hfsc_install__(struct netdev *netdev, uint32_t max_rate)
+{
+ struct netdev_dev_linux * netdev_dev;
+ struct hfsc *hfsc;
+
+ netdev_dev = netdev_dev_linux_cast(netdev_get_dev(netdev));
+ hfsc = xmalloc(sizeof *hfsc);
+ tc_init(&hfsc->tc, &tc_ops_hfsc);
+ hfsc->max_rate = max_rate;
+ netdev_dev->tc = &hfsc->tc;
+}
+
+static void
+hfsc_update_queue__(struct netdev *netdev, unsigned int queue_id,
+ const struct hfsc_class *hc)
+{
+ size_t hash;
+ struct hfsc *hfsc;
+ struct hfsc_class *hcp;
+ struct tc_queue *queue;
+
+ hfsc = hfsc_get__(netdev);
+ hash = hash_int(queue_id, 0);
+
+ queue = tc_find_queue__(netdev, queue_id, hash);
+ if (queue) {
+ hcp = hfsc_class_cast__(queue);
+ } else {
+ hcp = xmalloc(sizeof *hcp);
+ queue = &hcp->tc_queue;
+ queue->queue_id = queue_id;
+ hmap_insert(&hfsc->tc.queues, &queue->hmap_node, hash);
+ }
+
+ hcp->min_rate = hc->min_rate;
+ hcp->max_rate = hc->max_rate;
+}
+
+static int
+hfsc_parse_tca_options__(struct nlattr *nl_options, struct hfsc_class *class)
+{
+ const struct tc_service_curve *rsc, *fsc, *usc;
+ static const struct nl_policy tca_hfsc_policy[] = {
+ [TCA_HFSC_RSC] = {
+ .type = NL_A_UNSPEC,
+ .optional = false,
+ .min_len = sizeof(struct tc_service_curve),
+ },
+ [TCA_HFSC_FSC] = {
+ .type = NL_A_UNSPEC,
+ .optional = false,
+ .min_len = sizeof(struct tc_service_curve),
+ },
+ [TCA_HFSC_USC] = {
+ .type = NL_A_UNSPEC,
+ .optional = false,
+ .min_len = sizeof(struct tc_service_curve),
+ },
+ };
+ struct nlattr *attrs[ARRAY_SIZE(tca_hfsc_policy)];
+
+ if (!nl_parse_nested(nl_options, tca_hfsc_policy,
+ attrs, ARRAY_SIZE(tca_hfsc_policy))) {
+ VLOG_WARN_RL(&rl, "failed to parse HFSC class options");
+ return EPROTO;
+ }
+
+ rsc = nl_attr_get(attrs[TCA_HFSC_RSC]);
+ fsc = nl_attr_get(attrs[TCA_HFSC_FSC]);
+ usc = nl_attr_get(attrs[TCA_HFSC_USC]);
+
+ if (rsc->m1 != 0 || rsc->d != 0 ||
+ fsc->m1 != 0 || fsc->d != 0 ||
+ usc->m1 != 0 || usc->d != 0) {
+ VLOG_WARN_RL(&rl, "failed to parse HFSC class options. "
+ "Non-linear service curves are not supported.");
+ return EPROTO;
+ }
+
+ if (rsc->m2 != fsc->m2) {
+ VLOG_WARN_RL(&rl, "failed to parse HFSC class options. "
+ "Real-time service curves are not supported ");
+ return EPROTO;
+ }
+
+ if (rsc->m2 > usc->m2) {
+ VLOG_WARN_RL(&rl, "failed to parse HFSC class options. "
+ "Min-rate service curve is greater than "
+ "the max-rate service curve.");
+ return EPROTO;
+ }
+
+ class->min_rate = fsc->m2;
+ class->max_rate = usc->m2;
+ return 0;
+}
+
+static int
+hfsc_parse_tcmsg__(struct ofpbuf *tcmsg, unsigned int *queue_id,
+ struct hfsc_class *options,
+ struct netdev_queue_stats *stats)
+{
+ int error;
+ unsigned int handle;
+ struct nlattr *nl_options;
+
+ error = tc_parse_class(tcmsg, &handle, &nl_options, stats);
+ if (error) {
+ return error;
+ }
+
+ if (queue_id) {
+ unsigned int major, minor;
+
+ major = tc_get_major(handle);
+ minor = tc_get_minor(handle);
+ if (major == 1 && minor > 0 && minor <= HFSC_N_QUEUES) {
+ *queue_id = minor - 1;
+ } else {
+ return EPROTO;
+ }
+ }
+
+ if (options) {
+ error = hfsc_parse_tca_options__(nl_options, options);
+ }
+
+ return error;
+}
+
+static int
+hfsc_query_class__(const struct netdev *netdev, unsigned int handle,
+ unsigned int parent, struct hfsc_class *options,
+ struct netdev_queue_stats *stats)
+{
+ int error;
+ struct ofpbuf *reply;
+
+ error = tc_query_class(netdev, handle, parent, &reply);
+ if (error) {
+ return error;
+ }
+
+ error = hfsc_parse_tcmsg__(reply, NULL, options, stats);
+ ofpbuf_delete(reply);
+ return error;
+}
+
+static void
+hfsc_parse_qdisc_details__(struct netdev *netdev, const struct smap *details,
+ struct hfsc_class *class)
+{
+ uint32_t max_rate;
+ const char *max_rate_s;
+
+ max_rate_s = smap_get(details, "max-rate");
+ max_rate = max_rate_s ? strtoull(max_rate_s, NULL, 10) / 8 : 0;
+
+ if (!max_rate) {
+ enum netdev_features current;
+
+ netdev_get_features(netdev, ¤t, NULL, NULL, NULL);
+ max_rate = netdev_features_to_bps(current) / 8;
+ }
+
+ class->min_rate = max_rate;
+ class->max_rate = max_rate;
+}
+
+static int
+hfsc_parse_class_details__(struct netdev *netdev,
+ const struct smap *details,
+ struct hfsc_class * class)
+{
+ const struct hfsc *hfsc;
+ uint32_t min_rate, max_rate;
+ const char *min_rate_s, *max_rate_s;
+
+ hfsc = hfsc_get__(netdev);
+ min_rate_s = smap_get(details, "min-rate");
+ max_rate_s = smap_get(details, "max-rate");
+
+ min_rate = min_rate_s ? strtoull(min_rate_s, NULL, 10) / 8 : 0;
+ min_rate = MAX(min_rate, 1);
+ min_rate = MIN(min_rate, hfsc->max_rate);
+
+ max_rate = (max_rate_s
+ ? strtoull(max_rate_s, NULL, 10) / 8
+ : hfsc->max_rate);
+ max_rate = MAX(max_rate, min_rate);
+ max_rate = MIN(max_rate, hfsc->max_rate);
+
+ class->min_rate = min_rate;
+ class->max_rate = max_rate;
+
+ return 0;
+}
+
+/* Create an HFSC qdisc.
+ *
+ * Equivalent to "tc qdisc add dev <dev> root handle 1: hfsc default 1". */
+static int
+hfsc_setup_qdisc__(struct netdev * netdev)