*/
 
 #include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
 #include <linux/types.h>
 #include <linux/slab.h>
 #include <linux/kernel.h>
        struct list_head taprio_list;
        int cur_txq[TC_MAX_QUEUE];
        u32 max_sdu[TC_MAX_QUEUE]; /* save info from the user */
+       u32 fp[TC_QOPT_MAX_QUEUE]; /* only for dump and offloading */
        u32 txtime_delay;
 };
 
 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 },
+       [TCA_TAPRIO_TC_ENTRY_FP]           = NLA_POLICY_RANGE(NLA_U32,
+                                                             TC_FP_EXPRESS,
+                                                             TC_FP_PREEMPTIBLE),
 };
 
 static const struct nla_policy taprio_policy[TCA_TAPRIO_ATTR_MAX + 1] = {
        mqprio_qopt_reconstruct(dev, &offload->mqprio.qopt);
        offload->mqprio.extack = extack;
        taprio_sched_to_offload(dev, sched, offload, &caps);
+       mqprio_fp_to_offload(q->fp, &offload->mqprio);
 
        for (tc = 0; tc < TC_MAX_QUEUE; tc++)
                offload->max_sdu[tc] = q->max_sdu[tc];
 static int taprio_parse_tc_entry(struct Qdisc *sch,
                                 struct nlattr *opt,
                                 u32 max_sdu[TC_QOPT_MAX_QUEUE],
+                                u32 fp[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;
+       u32 val;
 
        err = nla_parse_nested(tb, TCA_TAPRIO_TC_ENTRY_MAX, opt,
                               taprio_tc_policy, extack);
 
        *seen_tcs |= BIT(tc);
 
-       if (tb[TCA_TAPRIO_TC_ENTRY_MAX_SDU])
+       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;
+               }
 
-       if (val > dev->max_mtu) {
-               NL_SET_ERR_MSG_MOD(extack, "TC max SDU exceeds device max MTU");
-               return -ERANGE;
+               max_sdu[tc] = val;
        }
 
-       max_sdu[tc] = val;
+       if (tb[TCA_TAPRIO_TC_ENTRY_FP])
+               fp[tc] = nla_get_u32(tb[TCA_TAPRIO_TC_ENTRY_FP]);
 
        return 0;
 }
                                   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];
+       bool have_preemption = false;
        unsigned long seen_tcs = 0;
+       u32 fp[TC_QOPT_MAX_QUEUE];
        struct nlattr *n;
        int tc, rem;
        int err = 0;
 
-       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
+       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
                max_sdu[tc] = q->max_sdu[tc];
+               fp[tc] = q->fp[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,
+               err = taprio_parse_tc_entry(sch, n, max_sdu, fp, &seen_tcs,
                                            extack);
                if (err)
-                       goto out;
+                       return err;
        }
 
-       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
+       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) {
                q->max_sdu[tc] = max_sdu[tc];
+               q->fp[tc] = fp[tc];
+               if (fp[tc] != TC_FP_EXPRESS)
+                       have_preemption = true;
+       }
+
+       if (have_preemption) {
+               if (!FULL_OFFLOAD_IS_ENABLED(q->flags)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Preemption only supported with full offload");
+                       return -EOPNOTSUPP;
+               }
+
+               if (!ethtool_dev_mm_supported(dev)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Device does not support preemption");
+                       return -EOPNOTSUPP;
+               }
+       }
 
-out:
        return err;
 }
 
 {
        struct taprio_sched *q = qdisc_priv(sch);
        struct net_device *dev = qdisc_dev(sch);
-       int i;
+       int i, tc;
 
        spin_lock_init(&q->current_entry_lock);
 
                q->qdiscs[i] = qdisc;
        }
 
+       for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++)
+               q->fp[tc] = TC_FP_EXPRESS;
+
        taprio_detect_broken_mqprio(q);
 
        return taprio_change(sch, opt, extack);
 }
 
 static int taprio_dump_tc_entries(struct sk_buff *skb,
+                                 struct taprio_sched *q,
                                  struct sched_gate_list *sched)
 {
        struct nlattr *n;
                                sched->max_sdu[tc]))
                        goto nla_put_failure;
 
+               if (nla_put_u32(skb, TCA_TAPRIO_TC_ENTRY_FP, q->fp[tc]))
+                       goto nla_put_failure;
+
                nla_nest_end(skb, n);
        }
 
            nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay))
                goto options_error;
 
-       if (oper && taprio_dump_tc_entries(skb, oper))
+       if (oper && taprio_dump_tc_entries(skb, q, oper))
                goto options_error;
 
        if (oper && dump_schedule(skb, oper))