struct sched_gate_list __rcu *admin_sched;
        struct hrtimer advance_timer;
        struct list_head taprio_list;
+       u32 max_frm_len[TC_MAX_QUEUE]; /* for the fast path */
+       u32 max_sdu[TC_MAX_QUEUE]; /* for dump and offloading */
        u32 txtime_delay;
 };
 
                              struct Qdisc *child, struct sk_buff **to_free)
 {
        struct taprio_sched *q = qdisc_priv(sch);
+       struct net_device *dev = qdisc_dev(sch);
+       int prio = skb->priority;
+       u8 tc;
 
        /* sk_flags are only safe to use on full sockets. */
        if (skb->sk && sk_fullsock(skb->sk) && sock_flag(skb->sk, SOCK_TXTIME)) {
                        return qdisc_drop(skb, sch, to_free);
        }
 
+       /* Devices with full offload are expected to honor this in hardware */
+       tc = netdev_get_prio_tc_map(dev, prio);
+       if (skb->len > q->max_frm_len[tc])
+               return qdisc_drop(skb, sch, to_free);
+
        qdisc_qstats_backlog_inc(sch, skb);
        sch->q.qlen++;
 
        [TCA_TAPRIO_SCHED_ENTRY_INTERVAL]  = { .type = NLA_U32 },
 };
 
+static const struct nla_policy taprio_tc_policy[TCA_TAPRIO_TC_ENTRY_MAX + 1] = {
+       [TCA_TAPRIO_TC_ENTRY_INDEX]        = { .type = NLA_U32 },
+       [TCA_TAPRIO_TC_ENTRY_MAX_SDU]      = { .type = NLA_U32 },
+};
+
 static const struct nla_policy taprio_policy[TCA_TAPRIO_ATTR_MAX + 1] = {
        [TCA_TAPRIO_ATTR_PRIOMAP]              = {
                .len = sizeof(struct tc_mqprio_qopt)
        [TCA_TAPRIO_ATTR_SCHED_CYCLE_TIME_EXTENSION] = { .type = NLA_S64 },
        [TCA_TAPRIO_ATTR_FLAGS]                      = { .type = NLA_U32 },
        [TCA_TAPRIO_ATTR_TXTIME_DELAY]               = { .type = NLA_U32 },
+       [TCA_TAPRIO_ATTR_TC_ENTRY]                   = { .type = NLA_NESTED },
 };
 
 static int fill_sched_entry(struct taprio_sched *q, struct nlattr **tb,
 {
        const struct net_device_ops *ops = dev->netdev_ops;
        struct tc_taprio_qopt_offload *offload;
-       int err = 0;
+       struct tc_taprio_caps caps;
+       int tc, err = 0;
 
        if (!ops->ndo_setup_tc) {
                NL_SET_ERR_MSG(extack,
                return -EOPNOTSUPP;
        }
 
+       qdisc_offload_query_caps(dev, TC_SETUP_QDISC_TAPRIO,
+                                &caps, sizeof(caps));
+
+       if (!caps.supports_queue_max_sdu) {
+               for (tc = 0; tc < TC_MAX_QUEUE; tc++) {
+                       if (q->max_sdu[tc]) {
+                               NL_SET_ERR_MSG_MOD(extack,
+                                                  "Device does not handle queueMaxSDU");
+                               return -EOPNOTSUPP;
+                       }
+               }
+       }
+
        offload = taprio_offload_alloc(sched->num_entries);
        if (!offload) {
                NL_SET_ERR_MSG(extack,
        offload->enable = 1;
        taprio_sched_to_offload(dev, sched, offload);
 
+       for (tc = 0; tc < TC_MAX_QUEUE; tc++)
+               offload->max_sdu[tc] = q->max_sdu[tc];
+
        err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_TAPRIO, offload);
        if (err < 0) {
                NL_SET_ERR_MSG(extack,
        return err;
 }
 
+static int taprio_parse_tc_entry(struct Qdisc *sch,
+                                struct nlattr *opt,
+                                u32 max_sdu[TC_QOPT_MAX_QUEUE],
+                                unsigned long *seen_tcs,
+                                struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[TCA_TAPRIO_TC_ENTRY_MAX + 1] = { };
+       struct net_device *dev = qdisc_dev(sch);
+       u32 val = 0;
+       int err, tc;
+
+       err = nla_parse_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, opt,
+                              taprio_tc_policy, extack);
+       if (err < 0)
+               return err;
+
+       if (!tb[TCA_TAPRIO_TC_ENTRY_INDEX]) {
+               NL_SET_ERR_MSG_MOD(extack, "TC entry index missing");
+               return -EINVAL;
+       }
+
+       tc = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_INDEX]);
+       if (tc >= TC_QOPT_MAX_QUEUE) {
+               NL_SET_ERR_MSG_MOD(extack, "TC entry index out of range");
+               return -ERANGE;
+       }
+
+       if (*seen_tcs & BIT(tc)) {
+               NL_SET_ERR_MSG_MOD(extack, "Duplicate TC entry");
+               return -EINVAL;
+       }
+
+       *seen_tcs |= BIT(tc);
+
+       if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU])
+               val = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU]);
+
+       if (val > dev->max_mtu) {
+               NL_SET_ERR_MSG_MOD(extack, "TC max SDU exceeds device max MTU");
+               return -ERANGE;
+       }
+
+       max_sdu[tc] = val;
+
+       return 0;
+}
+
+static int taprio_parse_tc_entries(struct Qdisc *sch,
+                                  struct nlattr *opt,
+                                  struct netlink_ext_ack *extack)
+{
+       struct taprio_sched *q = qdisc_priv(sch);
+       struct net_device *dev = qdisc_dev(sch);
+       u32 max_sdu[TC_QOPT_MAX_QUEUE];
+       unsigned long seen_tcs = 0;
+       struct nlattr *n;
+       int tc, rem;
+       int err = 0;
+
+       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
+               max_sdu[tc] = q->max_sdu[tc];
+
+       nla_for_each_nested(n, opt, rem) {
+               if (nla_type(n) != TCA_TAPRIO_ATTR_TC_ENTRY)
+                       continue;
+
+               err = taprio_parse_tc_entry(sch, n, max_sdu, &seen_tcs, extack);
+               if (err)
+                       goto out;
+       }
+
+       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
+               q->max_sdu[tc] = max_sdu[tc];
+               if (max_sdu[tc])
+                       q->max_frm_len[tc] = max_sdu[tc] + dev->hard_header_len;
+               else
+                       q->max_frm_len[tc] = U32_MAX; /* never oversized */
+       }
+
+out:
+       return err;
+}
+
 static int taprio_mqprio_cmp(const struct net_device *dev,
                             const struct tc_mqprio_qopt *mqprio)
 {
        if (err < 0)
                return err;
 
+       err = taprio_parse_tc_entries(sch, opt, extack);
+       if (err)
+               return err;
+
        new_admin = kzalloc(sizeof(*new_admin), GFP_KERNEL);
        if (!new_admin) {
                NL_SET_ERR_MSG(extack, "Not enough memory for a new schedule");
        return -1;
 }
 
+static int taprio_dump_tc_entries(struct taprio_sched *q, struct sk_buff *skb)
+{
+       struct nlattr *n;
+       int tc;
+
+       for (tc = 0; tc < TC_MAX_QUEUE; tc++) {
+               n = nla_nest_start(skb, TCA_TAPRIO_ATTR_TC_ENTRY);
+               if (!n)
+                       return -EMSGSIZE;
+
+               if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_INDEX, tc))
+                       goto nla_put_failure;
+
+               if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_MAX_SDU,
+                               q->max_sdu[tc]))
+                       goto nla_put_failure;
+
+               nla_nest_end(skb, n);
+       }
+
+       return 0;
+
+nla_put_failure:
+       nla_nest_cancel(skb, n);
+       return -EMSGSIZE;
+}
+
 static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb)
 {
        struct taprio_sched *q = qdisc_priv(sch);
            nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay))
                goto options_error;
 
+       if (taprio_dump_tc_entries(q, skb))
+               goto options_error;
+
        if (oper && dump_schedule(skb, oper))
                goto options_error;