#include <linux/kernel.h>
 #include <linux/skbuff.h>
 #include <net/pkt_sched.h>
+#include <net/pkt_cls.h>
 #include <net/inet_ecn.h>
 #include <net/red.h>
 
        red_restart(&q->vars);
 }
 
+static int red_offload(struct Qdisc *sch, bool enable)
+{
+       struct red_sched_data *q = qdisc_priv(sch);
+       struct net_device *dev = qdisc_dev(sch);
+       struct tc_red_qopt_offload opt = {
+               .handle = sch->handle,
+               .parent = sch->parent,
+       };
+
+       if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc)
+               return -EOPNOTSUPP;
+
+       if (enable) {
+               opt.command = TC_RED_REPLACE;
+               opt.set.min = q->parms.qth_min >> q->parms.Wlog;
+               opt.set.max = q->parms.qth_max >> q->parms.Wlog;
+               opt.set.probability = q->parms.max_P;
+               opt.set.is_ecn = red_use_ecn(q);
+       } else {
+               opt.command = TC_RED_DESTROY;
+       }
+
+       return dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED, &opt);
+}
+
 static void red_destroy(struct Qdisc *sch)
 {
        struct red_sched_data *q = qdisc_priv(sch);
 
        del_timer_sync(&q->adapt_timer);
+       red_offload(sch, false);
        qdisc_destroy(q->qdisc);
 }
 
                red_start_of_idle_period(&q->vars);
 
        sch_tree_unlock(sch);
+       red_offload(sch, true);
        return 0;
 }
 
        return red_change(sch, opt);
 }
 
+static int red_dump_offload(struct Qdisc *sch, struct tc_red_qopt *opt)
+{
+       struct net_device *dev = qdisc_dev(sch);
+       struct tc_red_qopt_offload hw_stats = {
+               .handle = sch->handle,
+               .parent = sch->parent,
+               .command = TC_RED_STATS,
+               .stats.bstats = &sch->bstats,
+               .stats.qstats = &sch->qstats,
+       };
+       int err;
+
+       opt->flags &= ~TC_RED_OFFLOADED;
+       if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc)
+               return 0;
+
+       err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_RED,
+                                           &hw_stats);
+       if (err == -EOPNOTSUPP)
+               return 0;
+
+       if (!err)
+               opt->flags |= TC_RED_OFFLOADED;
+
+       return err;
+}
+
 static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
 {
        struct red_sched_data *q = qdisc_priv(sch);
                .Plog           = q->parms.Plog,
                .Scell_log      = q->parms.Scell_log,
        };
+       int err;
 
        sch->qstats.backlog = q->qdisc->qstats.backlog;
+       err = red_dump_offload(sch, &opt);
+       if (err)
+               goto nla_put_failure;
+
        opts = nla_nest_start(skb, TCA_OPTIONS);
        if (opts == NULL)
                goto nla_put_failure;
 static int red_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
 {
        struct red_sched_data *q = qdisc_priv(sch);
+       struct net_device *dev = qdisc_dev(sch);
        struct tc_red_xstats st = {
                .early  = q->stats.prob_drop + q->stats.forced_drop,
                .pdrop  = q->stats.pdrop,
                .marked = q->stats.prob_mark + q->stats.forced_mark,
        };
 
+       if (tc_can_offload(dev) &&  dev->netdev_ops->ndo_setup_tc) {
+               struct red_stats hw_stats = {0};
+               struct tc_red_qopt_offload hw_stats_request = {
+                       .handle = sch->handle,
+                       .parent = sch->parent,
+                       .command = TC_RED_XSTATS,
+                       .xstats = &hw_stats,
+               };
+               if (!dev->netdev_ops->ndo_setup_tc(dev,
+                                                  TC_SETUP_QDISC_RED,
+                                                  &hw_stats_request)) {
+                       st.early += hw_stats.prob_drop + hw_stats.forced_drop;
+                       st.pdrop += hw_stats.pdrop;
+                       st.other += hw_stats.other;
+                       st.marked += hw_stats.prob_mark + hw_stats.forced_mark;
+               }
+       }
+
        return gnet_stats_copy_app(d, &st, sizeof(st));
 }