};
 #endif
 
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+/* Chain in tc_skb_ext will be used to share the tc chain with
+ * ovs recirc_id. It will be set to the current chain by tc
+ * and read by ovs to recirc_id.
+ */
+struct tc_skb_ext {
+       __u32 chain;
+};
+#endif
+
 struct sk_buff_head {
        /* These two members must be first. */
        struct sk_buff  *next;
 #endif
 #ifdef CONFIG_XFRM
        SKB_EXT_SEC_PATH,
+#endif
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+       TC_SKB_EXT,
 #endif
        SKB_EXT_NUM, /* must be last */
 };
 
 /* Allow datapath to associate multiple Netlink PIDs to each vport */
 #define OVS_DP_F_VPORT_PIDS    (1 << 1)
 
+/* Allow tc offload recirc sharing */
+#define OVS_DP_F_TC_RECIRC_SHARING     (1 << 2)
+
 /* Fixed logical ports. */
 #define OVSP_LOCAL      ((__u32)0)
 
 
 #ifdef CONFIG_XFRM
        [SKB_EXT_SEC_PATH] = SKB_EXT_CHUNKSIZEOF(struct sec_path),
 #endif
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+       [TC_SKB_EXT] = SKB_EXT_CHUNKSIZEOF(struct tc_skb_ext),
+#endif
 };
 
 static __always_inline unsigned int skb_ext_total_length(void)
 #endif
 #ifdef CONFIG_XFRM
                skb_ext_type_len[SKB_EXT_SEC_PATH] +
+#endif
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+               skb_ext_type_len[TC_SKB_EXT] +
 #endif
                0;
 }
 
        dp->user_features = 0;
 }
 
-static void ovs_dp_change(struct datapath *dp, struct nlattr *a[])
+DEFINE_STATIC_KEY_FALSE(tc_recirc_sharing_support);
+
+static int ovs_dp_change(struct datapath *dp, struct nlattr *a[])
 {
-       if (a[OVS_DP_ATTR_USER_FEATURES])
-               dp->user_features = nla_get_u32(a[OVS_DP_ATTR_USER_FEATURES]);
+       u32 user_features = 0;
+
+       if (a[OVS_DP_ATTR_USER_FEATURES]) {
+               user_features = nla_get_u32(a[OVS_DP_ATTR_USER_FEATURES]);
+
+               if (user_features & ~(OVS_DP_F_VPORT_PIDS |
+                                     OVS_DP_F_UNALIGNED |
+                                     OVS_DP_F_TC_RECIRC_SHARING))
+                       return -EOPNOTSUPP;
+
+#if !IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+               if (user_features & OVS_DP_F_TC_RECIRC_SHARING)
+                       return -EOPNOTSUPP;
+#endif
+       }
+
+       dp->user_features = user_features;
+
+       if (dp->user_features & OVS_DP_F_TC_RECIRC_SHARING)
+               static_branch_enable(&tc_recirc_sharing_support);
+       else
+               static_branch_disable(&tc_recirc_sharing_support);
+
+       return 0;
 }
 
 static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)
        parms.port_no = OVSP_LOCAL;
        parms.upcall_portids = a[OVS_DP_ATTR_UPCALL_PID];
 
-       ovs_dp_change(dp, a);
+       err = ovs_dp_change(dp, a);
+       if (err)
+               goto err_destroy_meters;
 
        /* So far only local changes have been made, now need the lock. */
        ovs_lock();
        if (IS_ERR(dp))
                goto err_unlock_free;
 
-       ovs_dp_change(dp, info->attrs);
+       err = ovs_dp_change(dp, info->attrs);
+       if (err)
+               goto err_unlock_free;
 
        err = ovs_dp_cmd_fill_info(dp, reply, info->snd_portid,
                                   info->snd_seq, 0, OVS_DP_CMD_SET);
 
 extern struct notifier_block ovs_dp_device_notifier;
 extern struct genl_family dp_vport_genl_family;
 
+DECLARE_STATIC_KEY_FALSE(tc_recirc_sharing_support);
+
 void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key);
 void ovs_dp_detach_port(struct vport *);
 int ovs_dp_upcall(struct datapath *, struct sk_buff *,
 
 int ovs_flow_key_extract(const struct ip_tunnel_info *tun_info,
                         struct sk_buff *skb, struct sw_flow_key *key)
 {
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+       struct tc_skb_ext *tc_ext;
+#endif
        int res, err;
 
        /* Extract metadata from packet. */
        if (res < 0)
                return res;
        key->mac_proto = res;
+
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+       if (static_branch_unlikely(&tc_recirc_sharing_support)) {
+               tc_ext = skb_ext_find(skb, TC_SKB_EXT);
+               key->recirc_id = tc_ext ? tc_ext->chain : 0;
+       } else {
+               key->recirc_id = 0;
+       }
+#else
        key->recirc_id = 0;
+#endif
 
        err = key_extract(skb, key);
        if (!err)
 
         tristate "Support to encoding decoding skb tcindex on IFE action"
         depends on NET_ACT_IFE
 
+config NET_TC_SKB_EXT
+       bool "TC recirculation support"
+       depends on NET_CLS_ACT
+       default y if NET_CLS_ACT
+       select SKB_EXTENSIONS
+
+       help
+         Say Y here to allow tc chain misses to continue in OvS datapath in
+         the correct recirc_id, and hardware chain misses to continue in
+         the correct chain in tc software datapath.
+
+         Say N here if you won't be using tc<->ovs offload or tc chains offload.
+
 endif # NET_SCHED
 
 config NET_SCH_FIFO
 
                        goto reset;
                } else if (unlikely(TC_ACT_EXT_CMP(err, TC_ACT_GOTO_CHAIN))) {
                        first_tp = res->goto_tp;
+
+#if IS_ENABLED(CONFIG_NET_TC_SKB_EXT)
+                       {
+                               struct tc_skb_ext *ext;
+
+                               ext = skb_ext_add(skb, TC_SKB_EXT);
+                               if (WARN_ON_ONCE(!ext))
+                                       return TC_ACT_SHOT;
+
+                               ext->chain = err & TC_ACT_EXT_VAL_MASK;
+                       }
+#endif
                        goto reset;
                }
 #endif