enum tc_taprio_qopt_cmd {
        TAPRIO_CMD_REPLACE,
        TAPRIO_CMD_DESTROY,
+       TAPRIO_CMD_STATS,
+       TAPRIO_CMD_TC_STATS,
+};
+
+/**
+ * struct tc_taprio_qopt_stats - IEEE 802.1Qbv statistics
+ * @window_drops: Frames that were dropped because they were too large to be
+ *     transmitted in any of the allotted time windows (open gates) for their
+ *     traffic class.
+ * @tx_overruns: Frames still being transmitted by the MAC after the
+ *     transmission gate associated with their traffic class has closed.
+ *     Equivalent to `12.29.1.1.2 TransmissionOverrun` from 802.1Q-2018.
+ */
+struct tc_taprio_qopt_stats {
+       u64 window_drops;
+       u64 tx_overruns;
+};
+
+struct tc_taprio_qopt_tc_stats {
+       int tc;
+       struct tc_taprio_qopt_stats stats;
 };
 
 struct tc_taprio_sched_entry {
 };
 
 struct tc_taprio_qopt_offload {
-       struct tc_mqprio_qopt_offload mqprio;
-       struct netlink_ext_ack *extack;
        enum tc_taprio_qopt_cmd cmd;
-       ktime_t base_time;
-       u64 cycle_time;
-       u64 cycle_time_extension;
-       u32 max_sdu[TC_MAX_QUEUE];
 
-       size_t num_entries;
-       struct tc_taprio_sched_entry entries[];
+       union {
+               /* TAPRIO_CMD_STATS */
+               struct tc_taprio_qopt_stats stats;
+               /* TAPRIO_CMD_TC_STATS */
+               struct tc_taprio_qopt_tc_stats tc_stats;
+               /* TAPRIO_CMD_REPLACE */
+               struct {
+                       struct tc_mqprio_qopt_offload mqprio;
+                       struct netlink_ext_ack *extack;
+                       ktime_t base_time;
+                       u64 cycle_time;
+                       u64 cycle_time_extension;
+                       u32 max_sdu[TC_MAX_QUEUE];
+
+                       size_t num_entries;
+                       struct tc_taprio_sched_entry entries[];
+               };
+       };
 };
 
 #if IS_ENABLED(CONFIG_NET_SCH_TAPRIO)
 
 #include <net/sock.h>
 #include <net/tcp.h>
 
+#define TAPRIO_STAT_NOT_SET    (~0ULL)
+
 #include "sch_mqprio_lib.h"
 
 static LIST_HEAD(taprio_list);
        return -EMSGSIZE;
 }
 
+static int taprio_put_stat(struct sk_buff *skb, u64 val, u16 attrtype)
+{
+       if (val == TAPRIO_STAT_NOT_SET)
+               return 0;
+       if (nla_put_u64_64bit(skb, attrtype, val, TCA_TAPRIO_OFFLOAD_STATS_PAD))
+               return -EMSGSIZE;
+       return 0;
+}
+
+static int taprio_dump_xstats(struct Qdisc *sch, struct gnet_dump *d,
+                             struct tc_taprio_qopt_offload *offload,
+                             struct tc_taprio_qopt_stats *stats)
+{
+       struct net_device *dev = qdisc_dev(sch);
+       const struct net_device_ops *ops;
+       struct sk_buff *skb = d->skb;
+       struct nlattr *xstats;
+       int err;
+
+       ops = qdisc_dev(sch)->netdev_ops;
+
+       /* FIXME I could use qdisc_offload_dump_helper(), but that messes
+        * with sch->flags depending on whether the device reports taprio
+        * stats, and I'm not sure whether that's a good idea, considering
+        * that stats are optional to the offload itself
+        */
+       if (!ops->ndo_setup_tc)
+               return 0;
+
+       memset(stats, 0xff, sizeof(*stats));
+
+       err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload);
+       if (err == -EOPNOTSUPP)
+               return 0;
+       if (err)
+               return err;
+
+       xstats = nla_nest_start(skb, TCA_STATS_APP);
+       if (!xstats)
+               goto err;
+
+       if (taprio_put_stat(skb, stats->window_drops,
+                           TCA_TAPRIO_OFFLOAD_STATS_WINDOW_DROPS) ||
+           taprio_put_stat(skb, stats->tx_overruns,
+                           TCA_TAPRIO_OFFLOAD_STATS_TX_OVERRUNS))
+               goto err_cancel;
+
+       nla_nest_end(skb, xstats);
+
+       return 0;
+
+err_cancel:
+       nla_nest_cancel(skb, xstats);
+err:
+       return -EMSGSIZE;
+}
+
+static int taprio_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
+{
+       struct tc_taprio_qopt_offload offload = {
+               .cmd = TAPRIO_CMD_STATS,
+       };
+
+       return taprio_dump_xstats(sch, d, &offload, &offload.stats);
+}
+
 static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb)
 {
        struct taprio_sched *q = qdisc_priv(sch);
 {
        struct netdev_queue *dev_queue = taprio_queue_get(sch, cl);
        struct Qdisc *child = dev_queue->qdisc_sleeping;
+       struct tc_taprio_qopt_offload offload = {
+               .cmd = TAPRIO_CMD_TC_STATS,
+               .tc_stats = {
+                       .tc = cl - 1,
+               },
+       };
 
        if (gnet_stats_copy_basic(d, NULL, &child->bstats, true) < 0 ||
            qdisc_qstats_copy(d, child) < 0)
                return -1;
-       return 0;
+
+       return taprio_dump_xstats(sch, d, &offload, &offload.tc_stats.stats);
 }
 
 static void taprio_walk(struct Qdisc *sch, struct qdisc_walker *arg)
        .dequeue        = taprio_dequeue,
        .enqueue        = taprio_enqueue,
        .dump           = taprio_dump,
+       .dump_stats     = taprio_dump_stats,
        .owner          = THIS_MODULE,
 };