root/drivers/net/ethernet/netronome/nfp/flower/conntrack.c
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2021 Corigine, Inc. */

#include <net/tc_act/tc_csum.h>
#include <net/tc_act/tc_ct.h>

#include "conntrack.h"
#include "../nfp_port.h"

const struct rhashtable_params nfp_tc_ct_merge_params = {
        .head_offset            = offsetof(struct nfp_fl_ct_tc_merge,
                                           hash_node),
        .key_len                = sizeof(unsigned long) * 2,
        .key_offset             = offsetof(struct nfp_fl_ct_tc_merge, cookie),
        .automatic_shrinking    = true,
};

const struct rhashtable_params nfp_nft_ct_merge_params = {
        .head_offset            = offsetof(struct nfp_fl_nft_tc_merge,
                                           hash_node),
        .key_len                = sizeof(unsigned long) * 3,
        .key_offset             = offsetof(struct nfp_fl_nft_tc_merge, cookie),
        .automatic_shrinking    = true,
};

static struct flow_action_entry *get_flow_act(struct flow_rule *rule,
                                              enum flow_action_id act_id);

/**
 * get_hashentry() - Wrapper around hashtable lookup.
 * @ht:         hashtable where entry could be found
 * @key:        key to lookup
 * @params:     hashtable params
 * @size:       size of entry to allocate if not in table
 *
 * Returns an entry from a hashtable. If entry does not exist
 * yet allocate the memory for it and return the new entry.
 */
static void *get_hashentry(struct rhashtable *ht, void *key,
                           const struct rhashtable_params params, size_t size)
{
        void *result;

        result = rhashtable_lookup_fast(ht, key, params);

        if (result)
                return result;

        result = kzalloc(size, GFP_KERNEL);
        if (!result)
                return ERR_PTR(-ENOMEM);

        return result;
}

bool is_pre_ct_flow(struct flow_cls_offload *flow)
{
        struct flow_rule *rule = flow_cls_offload_flow_rule(flow);
        struct flow_dissector *dissector = rule->match.dissector;
        struct flow_action_entry *act;
        struct flow_match_ct ct;
        int i;

        if (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT)) {
                flow_rule_match_ct(rule, &ct);
                if (ct.key->ct_state)
                        return false;
        }

        if (flow->common.chain_index)
                return false;

        flow_action_for_each(i, act, &flow->rule->action) {
                if (act->id == FLOW_ACTION_CT) {
                        /* The pre_ct rule only have the ct or ct nat action, cannot
                         * contains other ct action e.g ct commit and so on.
                         */
                        if ((!act->ct.action || act->ct.action == TCA_CT_ACT_NAT))
                                return true;
                        else
                                return false;
                }
        }

        return false;
}

bool is_post_ct_flow(struct flow_cls_offload *flow)
{
        struct flow_rule *rule = flow_cls_offload_flow_rule(flow);
        struct flow_dissector *dissector = rule->match.dissector;
        struct flow_action_entry *act;
        bool exist_ct_clear = false;
        struct flow_match_ct ct;
        int i;

        if (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT)) {
                flow_rule_match_ct(rule, &ct);
                if (ct.key->ct_state & TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED)
                        return true;
        } else {
                /* post ct entry cannot contains any ct action except ct_clear. */
                flow_action_for_each(i, act, &flow->rule->action) {
                        if (act->id == FLOW_ACTION_CT) {
                                /* ignore ct clear action. */
                                if (act->ct.action == TCA_CT_ACT_CLEAR) {
                                        exist_ct_clear = true;
                                        continue;
                                }

                                return false;
                        }
                }
                /* when do nat with ct, the post ct entry ignore the ct status,
                 * will match the nat field(sip/dip) instead. In this situation,
                 * the flow chain index is not zero and contains ct clear action.
                 */
                if (flow->common.chain_index && exist_ct_clear)
                        return true;
        }

        return false;
}

/**
 * get_mangled_key() - Mangle the key if mangle act exists
 * @rule:       rule that carries the actions
 * @buf:        pointer to key to be mangled
 * @offset:     used to adjust mangled offset in L2/L3/L4 header
 * @key_sz:     key size
 * @htype:      mangling type
 *
 * Returns buf where the mangled key stores.
 */
static void *get_mangled_key(struct flow_rule *rule, void *buf,
                             u32 offset, size_t key_sz,
                             enum flow_action_mangle_base htype)
{
        struct flow_action_entry *act;
        u32 *val = (u32 *)buf;
        u32 off, msk, key;
        int i;

        flow_action_for_each(i, act, &rule->action) {
                if (act->id == FLOW_ACTION_MANGLE &&
                    act->mangle.htype == htype) {
                        off = act->mangle.offset - offset;
                        msk = act->mangle.mask;
                        key = act->mangle.val;

                        /* Mangling is supposed to be u32 aligned */
                        if (off % 4 || off >= key_sz)
                                continue;

                        val[off >> 2] &= msk;
                        val[off >> 2] |= key;
                }
        }

        return buf;
}

/* Only tos and ttl are involved in flow_match_ip structure, which
 * doesn't conform to the layout of ip/ipv6 header definition. So
 * they need particular process here: fill them into the ip/ipv6
 * header, so that mangling actions can work directly.
 */
#define NFP_IPV4_TOS_MASK       GENMASK(23, 16)
#define NFP_IPV4_TTL_MASK       GENMASK(31, 24)
#define NFP_IPV6_TCLASS_MASK    GENMASK(27, 20)
#define NFP_IPV6_HLIMIT_MASK    GENMASK(7, 0)
static void *get_mangled_tos_ttl(struct flow_rule *rule, void *buf,
                                 bool is_v6)
{
        struct flow_match_ip match;
        /* IPv4's ttl field is in third dword. */
        __be32 ip_hdr[3];
        u32 tmp, hdr_len;

        flow_rule_match_ip(rule, &match);

        if (is_v6) {
                tmp = FIELD_PREP(NFP_IPV6_TCLASS_MASK, match.key->tos);
                ip_hdr[0] = cpu_to_be32(tmp);
                tmp = FIELD_PREP(NFP_IPV6_HLIMIT_MASK, match.key->ttl);
                ip_hdr[1] = cpu_to_be32(tmp);
                hdr_len = 2 * sizeof(__be32);
        } else {
                tmp = FIELD_PREP(NFP_IPV4_TOS_MASK, match.key->tos);
                ip_hdr[0] = cpu_to_be32(tmp);
                tmp = FIELD_PREP(NFP_IPV4_TTL_MASK, match.key->ttl);
                ip_hdr[2] = cpu_to_be32(tmp);
                hdr_len = 3 * sizeof(__be32);
        }

        get_mangled_key(rule, ip_hdr, 0, hdr_len,
                        is_v6 ? FLOW_ACT_MANGLE_HDR_TYPE_IP6 :
                                FLOW_ACT_MANGLE_HDR_TYPE_IP4);

        match.key = buf;

        if (is_v6) {
                tmp = be32_to_cpu(ip_hdr[0]);
                match.key->tos = FIELD_GET(NFP_IPV6_TCLASS_MASK, tmp);
                tmp = be32_to_cpu(ip_hdr[1]);
                match.key->ttl = FIELD_GET(NFP_IPV6_HLIMIT_MASK, tmp);
        } else {
                tmp = be32_to_cpu(ip_hdr[0]);
                match.key->tos = FIELD_GET(NFP_IPV4_TOS_MASK, tmp);
                tmp = be32_to_cpu(ip_hdr[2]);
                match.key->ttl = FIELD_GET(NFP_IPV4_TTL_MASK, tmp);
        }

        return buf;
}

/* Note entry1 and entry2 are not swappable. only skip ip and
 * tport merge check for pre_ct and post_ct when pre_ct do nat.
 */
static bool nfp_ct_merge_check_cannot_skip(struct nfp_fl_ct_flow_entry *entry1,
                                           struct nfp_fl_ct_flow_entry *entry2)
{
        /* only pre_ct have NFP_FL_ACTION_DO_NAT flag. */
        if ((entry1->flags & NFP_FL_ACTION_DO_NAT) &&
            entry2->type == CT_TYPE_POST_CT)
                return false;

        return true;
}

/* Note entry1 and entry2 are not swappable, entry1 should be
 * the former flow whose mangle action need be taken into account
 * if existed, and entry2 should be the latter flow whose action
 * we don't care.
 */
static int nfp_ct_merge_check(struct nfp_fl_ct_flow_entry *entry1,
                              struct nfp_fl_ct_flow_entry *entry2)
{
        unsigned long long ovlp_keys;
        bool out, is_v6 = false;
        u8 ip_proto = 0;
        ovlp_keys = entry1->rule->match.dissector->used_keys &
                        entry2->rule->match.dissector->used_keys;
        /* Temporary buffer for mangling keys, 64 is enough to cover max
         * struct size of key in various fields that may be mangled.
         * Supported fields to mangle:
         * mac_src/mac_dst(struct flow_match_eth_addrs, 12B)
         * nw_tos/nw_ttl(struct flow_match_ip, 2B)
         * nw_src/nw_dst(struct flow_match_ipv4/6_addrs, 32B)
         * tp_src/tp_dst(struct flow_match_ports, 4B)
         */
        char buf[64];

        if (entry1->netdev && entry2->netdev &&
            entry1->netdev != entry2->netdev)
                return -EINVAL;

        /* Check the overlapped fields one by one, the unmasked part
         * should not conflict with each other.
         */
        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL)) {
                struct flow_match_control match1, match2;

                flow_rule_match_control(entry1->rule, &match1);
                flow_rule_match_control(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_BASIC)) {
                struct flow_match_basic match1, match2;

                flow_rule_match_basic(entry1->rule, &match1);
                flow_rule_match_basic(entry2->rule, &match2);

                /* n_proto field is a must in ct-related flows,
                 * it should be either ipv4 or ipv6.
                 */
                is_v6 = match1.key->n_proto == htons(ETH_P_IPV6);
                /* ip_proto field is a must when port field is cared */
                ip_proto = match1.key->ip_proto;

                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        /* if pre ct entry do nat, the nat ip exists in nft entry,
         * will be do merge check when do nft and post ct merge,
         * so skip this ip merge check here.
         */
        if ((ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS)) &&
            nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
                struct flow_match_ipv4_addrs match1, match2;

                flow_rule_match_ipv4_addrs(entry1->rule, &match1);
                flow_rule_match_ipv4_addrs(entry2->rule, &match2);

                memcpy(buf, match1.key, sizeof(*match1.key));
                match1.key = get_mangled_key(entry1->rule, buf,
                                             offsetof(struct iphdr, saddr),
                                             sizeof(*match1.key),
                                             FLOW_ACT_MANGLE_HDR_TYPE_IP4);

                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        /* if pre ct entry do nat, the nat ip exists in nft entry,
         * will be do merge check when do nft and post ct merge,
         * so skip this ip merge check here.
         */
        if ((ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS)) &&
            nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
                struct flow_match_ipv6_addrs match1, match2;

                flow_rule_match_ipv6_addrs(entry1->rule, &match1);
                flow_rule_match_ipv6_addrs(entry2->rule, &match2);

                memcpy(buf, match1.key, sizeof(*match1.key));
                match1.key = get_mangled_key(entry1->rule, buf,
                                             offsetof(struct ipv6hdr, saddr),
                                             sizeof(*match1.key),
                                             FLOW_ACT_MANGLE_HDR_TYPE_IP6);

                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        /* if pre ct entry do nat, the nat tport exists in nft entry,
         * will be do merge check when do nft and post ct merge,
         * so skip this tport merge check here.
         */
        if ((ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_PORTS)) &&
            nfp_ct_merge_check_cannot_skip(entry1, entry2)) {
                enum flow_action_mangle_base htype = FLOW_ACT_MANGLE_UNSPEC;
                struct flow_match_ports match1, match2;

                flow_rule_match_ports(entry1->rule, &match1);
                flow_rule_match_ports(entry2->rule, &match2);

                if (ip_proto == IPPROTO_UDP)
                        htype = FLOW_ACT_MANGLE_HDR_TYPE_UDP;
                else if (ip_proto == IPPROTO_TCP)
                        htype = FLOW_ACT_MANGLE_HDR_TYPE_TCP;

                memcpy(buf, match1.key, sizeof(*match1.key));
                match1.key = get_mangled_key(entry1->rule, buf, 0,
                                             sizeof(*match1.key), htype);

                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
                struct flow_match_eth_addrs match1, match2;

                flow_rule_match_eth_addrs(entry1->rule, &match1);
                flow_rule_match_eth_addrs(entry2->rule, &match2);

                memcpy(buf, match1.key, sizeof(*match1.key));
                match1.key = get_mangled_key(entry1->rule, buf, 0,
                                             sizeof(*match1.key),
                                             FLOW_ACT_MANGLE_HDR_TYPE_ETH);

                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_VLAN)) {
                struct flow_match_vlan match1, match2;

                flow_rule_match_vlan(entry1->rule, &match1);
                flow_rule_match_vlan(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_MPLS)) {
                struct flow_match_mpls match1, match2;

                flow_rule_match_mpls(entry1->rule, &match1);
                flow_rule_match_mpls(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_TCP)) {
                struct flow_match_tcp match1, match2;

                flow_rule_match_tcp(entry1->rule, &match1);
                flow_rule_match_tcp(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_IP)) {
                struct flow_match_ip match1, match2;

                flow_rule_match_ip(entry1->rule, &match1);
                flow_rule_match_ip(entry2->rule, &match2);

                match1.key = get_mangled_tos_ttl(entry1->rule, buf, is_v6);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_KEYID)) {
                struct flow_match_enc_keyid match1, match2;

                flow_rule_match_enc_keyid(entry1->rule, &match1);
                flow_rule_match_enc_keyid(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV4_ADDRS)) {
                struct flow_match_ipv4_addrs match1, match2;

                flow_rule_match_enc_ipv4_addrs(entry1->rule, &match1);
                flow_rule_match_enc_ipv4_addrs(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IPV6_ADDRS)) {
                struct flow_match_ipv6_addrs match1, match2;

                flow_rule_match_enc_ipv6_addrs(entry1->rule, &match1);
                flow_rule_match_enc_ipv6_addrs(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_CONTROL)) {
                struct flow_match_control match1, match2;

                flow_rule_match_enc_control(entry1->rule, &match1);
                flow_rule_match_enc_control(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_IP)) {
                struct flow_match_ip match1, match2;

                flow_rule_match_enc_ip(entry1->rule, &match1);
                flow_rule_match_enc_ip(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        if (ovlp_keys & BIT_ULL(FLOW_DISSECTOR_KEY_ENC_OPTS)) {
                struct flow_match_enc_opts match1, match2;

                flow_rule_match_enc_opts(entry1->rule, &match1);
                flow_rule_match_enc_opts(entry2->rule, &match2);
                COMPARE_UNMASKED_FIELDS(match1, match2, &out);
                if (out)
                        goto check_failed;
        }

        return 0;

check_failed:
        return -EINVAL;
}

static int nfp_ct_check_vlan_merge(struct flow_action_entry *a_in,
                                   struct flow_rule *rule)
{
        struct flow_match_vlan match;

        if (unlikely(flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CVLAN)))
                return -EOPNOTSUPP;

        /* post_ct does not match VLAN KEY, can be merged. */
        if (likely(!flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)))
                return 0;

        switch (a_in->id) {
        /* pre_ct has pop vlan, post_ct cannot match VLAN KEY, cannot be merged. */
        case FLOW_ACTION_VLAN_POP:
                return -EOPNOTSUPP;

        case FLOW_ACTION_VLAN_PUSH:
        case FLOW_ACTION_VLAN_MANGLE:
                flow_rule_match_vlan(rule, &match);
                /* different vlan id, cannot be merged. */
                if ((match.key->vlan_id & match.mask->vlan_id) ^
                    (a_in->vlan.vid & match.mask->vlan_id))
                        return -EOPNOTSUPP;

                /* different tpid, cannot be merged. */
                if ((match.key->vlan_tpid & match.mask->vlan_tpid) ^
                    (a_in->vlan.proto & match.mask->vlan_tpid))
                        return -EOPNOTSUPP;

                /* different priority, cannot be merged. */
                if ((match.key->vlan_priority & match.mask->vlan_priority) ^
                    (a_in->vlan.prio & match.mask->vlan_priority))
                        return -EOPNOTSUPP;

                break;
        default:
                return -EOPNOTSUPP;
        }

        return 0;
}

/* Extra check for multiple ct-zones merge
 * currently surpport nft entries merge check in different zones
 */
static int nfp_ct_merge_extra_check(struct nfp_fl_ct_flow_entry *nft_entry,
                                    struct nfp_fl_ct_tc_merge *tc_m_entry)
{
        struct nfp_fl_nft_tc_merge *prev_nft_m_entry;
        struct nfp_fl_ct_flow_entry *pre_ct_entry;

        pre_ct_entry = tc_m_entry->pre_ct_parent;
        prev_nft_m_entry = pre_ct_entry->prev_m_entries[pre_ct_entry->num_prev_m_entries - 1];

        return nfp_ct_merge_check(prev_nft_m_entry->nft_parent, nft_entry);
}

static int nfp_ct_merge_act_check(struct nfp_fl_ct_flow_entry *pre_ct_entry,
                                  struct nfp_fl_ct_flow_entry *post_ct_entry,
                                  struct nfp_fl_ct_flow_entry *nft_entry)
{
        struct flow_action_entry *act;
        int i, err;

        /* Check for pre_ct->action conflicts */
        flow_action_for_each(i, act, &pre_ct_entry->rule->action) {
                switch (act->id) {
                case FLOW_ACTION_VLAN_PUSH:
                case FLOW_ACTION_VLAN_POP:
                case FLOW_ACTION_VLAN_MANGLE:
                        err = nfp_ct_check_vlan_merge(act, post_ct_entry->rule);
                        if (err)
                                return err;
                        break;
                case FLOW_ACTION_MPLS_PUSH:
                case FLOW_ACTION_MPLS_POP:
                case FLOW_ACTION_MPLS_MANGLE:
                        return -EOPNOTSUPP;
                default:
                        break;
                }
        }

        /* Check for nft->action conflicts */
        flow_action_for_each(i, act, &nft_entry->rule->action) {
                switch (act->id) {
                case FLOW_ACTION_VLAN_PUSH:
                case FLOW_ACTION_VLAN_POP:
                case FLOW_ACTION_VLAN_MANGLE:
                case FLOW_ACTION_MPLS_PUSH:
                case FLOW_ACTION_MPLS_POP:
                case FLOW_ACTION_MPLS_MANGLE:
                        return -EOPNOTSUPP;
                default:
                        break;
                }
        }
        return 0;
}

static int nfp_ct_check_meta(struct nfp_fl_ct_flow_entry *post_ct_entry,
                             struct nfp_fl_ct_flow_entry *nft_entry)
{
        struct flow_dissector *dissector = post_ct_entry->rule->match.dissector;
        struct flow_action_entry *ct_met;
        struct flow_match_ct ct;
        int i;

        ct_met = get_flow_act(nft_entry->rule, FLOW_ACTION_CT_METADATA);
        if (ct_met && (dissector->used_keys & BIT_ULL(FLOW_DISSECTOR_KEY_CT))) {
                u32 *act_lbl;

                act_lbl = ct_met->ct_metadata.labels;
                flow_rule_match_ct(post_ct_entry->rule, &ct);
                for (i = 0; i < 4; i++) {
                        if ((ct.key->ct_labels[i] & ct.mask->ct_labels[i]) ^
                            (act_lbl[i] & ct.mask->ct_labels[i]))
                                return -EINVAL;
                }

                if ((ct.key->ct_mark & ct.mask->ct_mark) ^
                    (ct_met->ct_metadata.mark & ct.mask->ct_mark))
                        return -EINVAL;

                return 0;
        } else {
                /* post_ct with ct clear action will not match the
                 * ct status when nft is nat entry.
                 */
                if (nft_entry->flags & NFP_FL_ACTION_DO_MANGLE)
                        return 0;
        }

        return -EINVAL;
}

static int
nfp_fl_calc_key_layers_sz(struct nfp_fl_key_ls in_key_ls, uint16_t *map)
{
        int key_size;

        /* This field must always be present */
        key_size = sizeof(struct nfp_flower_meta_tci);
        map[FLOW_PAY_META_TCI] = 0;

        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_EXT_META) {
                map[FLOW_PAY_EXT_META] = key_size;
                key_size += sizeof(struct nfp_flower_ext_meta);
        }
        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_PORT) {
                map[FLOW_PAY_INPORT] = key_size;
                key_size += sizeof(struct nfp_flower_in_port);
        }
        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_MAC) {
                map[FLOW_PAY_MAC_MPLS] = key_size;
                key_size += sizeof(struct nfp_flower_mac_mpls);
        }
        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_TP) {
                map[FLOW_PAY_L4] = key_size;
                key_size += sizeof(struct nfp_flower_tp_ports);
        }
        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_IPV4) {
                map[FLOW_PAY_IPV4] = key_size;
                key_size += sizeof(struct nfp_flower_ipv4);
        }
        if (in_key_ls.key_layer & NFP_FLOWER_LAYER_IPV6) {
                map[FLOW_PAY_IPV6] = key_size;
                key_size += sizeof(struct nfp_flower_ipv6);
        }

        if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_QINQ) {
                map[FLOW_PAY_QINQ] = key_size;
                key_size += sizeof(struct nfp_flower_vlan);
        }

        if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GRE) {
                map[FLOW_PAY_GRE] = key_size;
                if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6)
                        key_size += sizeof(struct nfp_flower_ipv6_gre_tun);
                else
                        key_size += sizeof(struct nfp_flower_ipv4_gre_tun);
        }

        if ((in_key_ls.key_layer & NFP_FLOWER_LAYER_VXLAN) ||
            (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GENEVE)) {
                map[FLOW_PAY_UDP_TUN] = key_size;
                if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6)
                        key_size += sizeof(struct nfp_flower_ipv6_udp_tun);
                else
                        key_size += sizeof(struct nfp_flower_ipv4_udp_tun);
        }

        if (in_key_ls.key_layer_two & NFP_FLOWER_LAYER2_GENEVE_OP) {
                map[FLOW_PAY_GENEVE_OPT] = key_size;
                key_size += sizeof(struct nfp_flower_geneve_options);
        }

        return key_size;
}

/* get the csum flag according the ip proto and mangle action. */
static void nfp_fl_get_csum_flag(struct flow_action_entry *a_in, u8 ip_proto, u32 *csum)
{
        if (a_in->id != FLOW_ACTION_MANGLE)
                return;

        switch (a_in->mangle.htype) {
        case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
                *csum |= TCA_CSUM_UPDATE_FLAG_IPV4HDR;
                if (ip_proto == IPPROTO_TCP)
                        *csum |= TCA_CSUM_UPDATE_FLAG_TCP;
                else if (ip_proto == IPPROTO_UDP)
                        *csum |= TCA_CSUM_UPDATE_FLAG_UDP;
                break;
        case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
                *csum |= TCA_CSUM_UPDATE_FLAG_TCP;
                break;
        case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
                *csum |= TCA_CSUM_UPDATE_FLAG_UDP;
                break;
        default:
                break;
        }
}

static int nfp_fl_merge_actions_offload(struct flow_rule **rules,
                                        struct nfp_flower_priv *priv,
                                        struct net_device *netdev,
                                        struct nfp_fl_payload *flow_pay,
                                        int num_rules)
{
        enum flow_action_hw_stats tmp_stats = FLOW_ACTION_HW_STATS_DONT_CARE;
        struct flow_action_entry *a_in;
        int i, j, id, num_actions = 0;
        struct flow_rule *a_rule;
        int err = 0, offset = 0;

        for (i = 0; i < num_rules; i++)
                num_actions += rules[i]->action.num_entries;

        /* Add one action to make sure there is enough room to add an checksum action
         * when do nat.
         */
        a_rule = flow_rule_alloc(num_actions + (num_rules / 2));
        if (!a_rule)
                return -ENOMEM;

        /* post_ct entry have one action at least. */
        if (rules[num_rules - 1]->action.num_entries != 0)
                tmp_stats = rules[num_rules - 1]->action.entries[0].hw_stats;

        /* Actions need a BASIC dissector. */
        a_rule->match = rules[0]->match;

        /* Copy actions */
        for (j = 0; j < num_rules; j++) {
                u32 csum_updated = 0;
                u8 ip_proto = 0;

                if (flow_rule_match_key(rules[j], FLOW_DISSECTOR_KEY_BASIC)) {
                        struct flow_match_basic match;

                        /* ip_proto is the only field that is needed in later compile_action,
                         * needed to set the correct checksum flags. It doesn't really matter
                         * which input rule's ip_proto field we take as the earlier merge checks
                         * would have made sure that they don't conflict. We do not know which
                         * of the subflows would have the ip_proto filled in, so we need to iterate
                         * through the subflows and assign the proper subflow to a_rule
                         */
                        flow_rule_match_basic(rules[j], &match);
                        if (match.mask->ip_proto) {
                                a_rule->match = rules[j]->match;
                                ip_proto = match.key->ip_proto;
                        }
                }

                for (i = 0; i < rules[j]->action.num_entries; i++) {
                        a_in = &rules[j]->action.entries[i];
                        id = a_in->id;

                        /* Ignore CT related actions as these would already have
                         * been taken care of by previous checks, and we do not send
                         * any CT actions to the firmware.
                         */
                        switch (id) {
                        case FLOW_ACTION_CT:
                        case FLOW_ACTION_GOTO:
                        case FLOW_ACTION_CT_METADATA:
                                continue;
                        default:
                                /* nft entry is generated by tc ct, which mangle action do not care
                                 * the stats, inherit the post entry stats to meet the
                                 * flow_action_hw_stats_check.
                                 * nft entry flow rules are at odd array index.
                                 */
                                if (j & 0x01) {
                                        if (a_in->hw_stats == FLOW_ACTION_HW_STATS_DONT_CARE)
                                                a_in->hw_stats = tmp_stats;
                                        nfp_fl_get_csum_flag(a_in, ip_proto, &csum_updated);
                                }
                                memcpy(&a_rule->action.entries[offset++],
                                       a_in, sizeof(struct flow_action_entry));
                                break;
                        }
                }
                /* nft entry have mangle action, but do not have checksum action when do NAT,
                 * hardware will automatically fix IPv4 and TCP/UDP checksum. so add an csum action
                 * to meet csum action check.
                 */
                if (csum_updated) {
                        struct flow_action_entry *csum_action;

                        csum_action = &a_rule->action.entries[offset++];
                        csum_action->id = FLOW_ACTION_CSUM;
                        csum_action->csum_flags = csum_updated;
                        csum_action->hw_stats = tmp_stats;
                }
        }

        /* Some actions would have been ignored, so update the num_entries field */
        a_rule->action.num_entries = offset;
        err = nfp_flower_compile_action(priv->app, a_rule, netdev, flow_pay, NULL);
        kfree(a_rule);

        return err;
}

static int nfp_fl_ct_add_offload(struct nfp_fl_nft_tc_merge *m_entry)
{
        enum nfp_flower_tun_type tun_type = NFP_FL_TUNNEL_NONE;
        struct nfp_fl_ct_zone_entry *zt = m_entry->zt;
        struct flow_rule *rules[NFP_MAX_ENTRY_RULES];
        struct nfp_fl_ct_flow_entry *pre_ct_entry;
        struct nfp_fl_key_ls key_layer, tmp_layer;
        struct nfp_flower_priv *priv = zt->priv;
        u16 key_map[_FLOW_PAY_LAYERS_MAX];
        struct nfp_fl_payload *flow_pay;
        u8 *key, *msk, *kdata, *mdata;
        struct nfp_port *port = NULL;
        int num_rules, err, i, j = 0;
        struct net_device *netdev;
        bool qinq_sup;
        u32 port_id;
        u16 offset;

        netdev = m_entry->netdev;
        qinq_sup = !!(priv->flower_ext_feats & NFP_FL_FEATS_VLAN_QINQ);

        pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent;
        num_rules = pre_ct_entry->num_prev_m_entries * 2 + _CT_TYPE_MAX;

        for (i = 0; i < pre_ct_entry->num_prev_m_entries; i++) {
                rules[j++] = pre_ct_entry->prev_m_entries[i]->tc_m_parent->pre_ct_parent->rule;
                rules[j++] = pre_ct_entry->prev_m_entries[i]->nft_parent->rule;
        }

        rules[j++] = m_entry->tc_m_parent->pre_ct_parent->rule;
        rules[j++] = m_entry->nft_parent->rule;
        rules[j++] = m_entry->tc_m_parent->post_ct_parent->rule;

        memset(&key_layer, 0, sizeof(struct nfp_fl_key_ls));
        memset(&key_map, 0, sizeof(key_map));

        /* Calculate the resultant key layer and size for offload */
        for (i = 0; i < num_rules; i++) {
                err = nfp_flower_calculate_key_layers(priv->app,
                                                      m_entry->netdev,
                                                      &tmp_layer, rules[i],
                                                      &tun_type, NULL);
                if (err)
                        return err;

                key_layer.key_layer |= tmp_layer.key_layer;
                key_layer.key_layer_two |= tmp_layer.key_layer_two;
        }
        key_layer.key_size = nfp_fl_calc_key_layers_sz(key_layer, key_map);

        flow_pay = nfp_flower_allocate_new(&key_layer);
        if (!flow_pay)
                return -ENOMEM;

        memset(flow_pay->unmasked_data, 0, key_layer.key_size);
        memset(flow_pay->mask_data, 0, key_layer.key_size);

        kdata = flow_pay->unmasked_data;
        mdata = flow_pay->mask_data;

        offset = key_map[FLOW_PAY_META_TCI];
        key = kdata + offset;
        msk = mdata + offset;
        nfp_flower_compile_meta((struct nfp_flower_meta_tci *)key,
                                (struct nfp_flower_meta_tci *)msk,
                                key_layer.key_layer);

        if (NFP_FLOWER_LAYER_EXT_META & key_layer.key_layer) {
                offset =  key_map[FLOW_PAY_EXT_META];
                key = kdata + offset;
                msk = mdata + offset;
                nfp_flower_compile_ext_meta((struct nfp_flower_ext_meta *)key,
                                            key_layer.key_layer_two);
                nfp_flower_compile_ext_meta((struct nfp_flower_ext_meta *)msk,
                                            key_layer.key_layer_two);
        }

        /* Using in_port from the -trk rule. The tc merge checks should already
         * be checking that the ingress netdevs are the same
         */
        port_id = nfp_flower_get_port_id_from_netdev(priv->app, netdev);
        offset = key_map[FLOW_PAY_INPORT];
        key = kdata + offset;
        msk = mdata + offset;
        err = nfp_flower_compile_port((struct nfp_flower_in_port *)key,
                                      port_id, false, tun_type, NULL);
        if (err)
                goto ct_offload_err;
        err = nfp_flower_compile_port((struct nfp_flower_in_port *)msk,
                                      port_id, true, tun_type, NULL);
        if (err)
                goto ct_offload_err;

        /* This following part works on the assumption that previous checks has
         * already filtered out flows that has different values for the different
         * layers. Here we iterate through all three rules and merge their respective
         * masked value(cared bits), basic method is:
         * final_key = (r1_key & r1_mask) | (r2_key & r2_mask) | (r3_key & r3_mask)
         * final_mask = r1_mask | r2_mask | r3_mask
         * If none of the rules contains a match that is also fine, that simply means
         * that the layer is not present.
         */
        if (!qinq_sup) {
                for (i = 0; i < num_rules; i++) {
                        offset = key_map[FLOW_PAY_META_TCI];
                        key = kdata + offset;
                        msk = mdata + offset;
                        nfp_flower_compile_tci((struct nfp_flower_meta_tci *)key,
                                               (struct nfp_flower_meta_tci *)msk,
                                               rules[i]);
                }
        }

        if (NFP_FLOWER_LAYER_MAC & key_layer.key_layer) {
                offset = key_map[FLOW_PAY_MAC_MPLS];
                key = kdata + offset;
                msk = mdata + offset;
                for (i = 0; i < num_rules; i++) {
                        nfp_flower_compile_mac((struct nfp_flower_mac_mpls *)key,
                                               (struct nfp_flower_mac_mpls *)msk,
                                               rules[i]);
                        err = nfp_flower_compile_mpls((struct nfp_flower_mac_mpls *)key,
                                                      (struct nfp_flower_mac_mpls *)msk,
                                                      rules[i], NULL);
                        if (err)
                                goto ct_offload_err;
                }
        }

        if (NFP_FLOWER_LAYER_IPV4 & key_layer.key_layer) {
                offset = key_map[FLOW_PAY_IPV4];
                key = kdata + offset;
                msk = mdata + offset;
                for (i = 0; i < num_rules; i++) {
                        nfp_flower_compile_ipv4((struct nfp_flower_ipv4 *)key,
                                                (struct nfp_flower_ipv4 *)msk,
                                                rules[i]);
                }
        }

        if (NFP_FLOWER_LAYER_IPV6 & key_layer.key_layer) {
                offset = key_map[FLOW_PAY_IPV6];
                key = kdata + offset;
                msk = mdata + offset;
                for (i = 0; i < num_rules; i++) {
                        nfp_flower_compile_ipv6((struct nfp_flower_ipv6 *)key,
                                                (struct nfp_flower_ipv6 *)msk,
                                                rules[i]);
                }
        }

        if (NFP_FLOWER_LAYER_TP & key_layer.key_layer) {
                offset = key_map[FLOW_PAY_L4];
                key = kdata + offset;
                msk = mdata + offset;
                for (i = 0; i < num_rules; i++) {
                        nfp_flower_compile_tport((struct nfp_flower_tp_ports *)key,
                                                 (struct nfp_flower_tp_ports *)msk,
                                                 rules[i]);
                }
        }

        if (NFP_FLOWER_LAYER2_QINQ & key_layer.key_layer_two) {
                offset = key_map[FLOW_PAY_QINQ];
                key = kdata + offset;
                msk = mdata + offset;
                for (i = 0; i < num_rules; i++) {
                        nfp_flower_compile_vlan((struct nfp_flower_vlan *)key,
                                                (struct nfp_flower_vlan *)msk,
                                                rules[i]);
                }
        }

        if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_GRE) {
                offset = key_map[FLOW_PAY_GRE];
                key = kdata + offset;
                msk = mdata + offset;
                if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) {
                        struct nfp_flower_ipv6_gre_tun *gre_match;
                        struct nfp_ipv6_addr_entry *entry;
                        struct in6_addr *dst;

                        for (i = 0; i < num_rules; i++) {
                                nfp_flower_compile_ipv6_gre_tun((void *)key,
                                                                (void *)msk, rules[i]);
                        }
                        gre_match = (struct nfp_flower_ipv6_gre_tun *)key;
                        dst = &gre_match->ipv6.dst;

                        entry = nfp_tunnel_add_ipv6_off(priv->app, dst);
                        if (!entry) {
                                err = -ENOMEM;
                                goto ct_offload_err;
                        }

                        flow_pay->nfp_tun_ipv6 = entry;
                } else {
                        __be32 dst;

                        for (i = 0; i < num_rules; i++) {
                                nfp_flower_compile_ipv4_gre_tun((void *)key,
                                                                (void *)msk, rules[i]);
                        }
                        dst = ((struct nfp_flower_ipv4_gre_tun *)key)->ipv4.dst;

                        /* Store the tunnel destination in the rule data.
                         * This must be present and be an exact match.
                         */
                        flow_pay->nfp_tun_ipv4_addr = dst;
                        nfp_tunnel_add_ipv4_off(priv->app, dst);
                }
        }

        if (key_layer.key_layer & NFP_FLOWER_LAYER_VXLAN ||
            key_layer.key_layer_two & NFP_FLOWER_LAYER2_GENEVE) {
                offset = key_map[FLOW_PAY_UDP_TUN];
                key = kdata + offset;
                msk = mdata + offset;
                if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_TUN_IPV6) {
                        struct nfp_flower_ipv6_udp_tun *udp_match;
                        struct nfp_ipv6_addr_entry *entry;
                        struct in6_addr *dst;

                        for (i = 0; i < num_rules; i++) {
                                nfp_flower_compile_ipv6_udp_tun((void *)key,
                                                                (void *)msk, rules[i]);
                        }
                        udp_match = (struct nfp_flower_ipv6_udp_tun *)key;
                        dst = &udp_match->ipv6.dst;

                        entry = nfp_tunnel_add_ipv6_off(priv->app, dst);
                        if (!entry) {
                                err = -ENOMEM;
                                goto ct_offload_err;
                        }

                        flow_pay->nfp_tun_ipv6 = entry;
                } else {
                        __be32 dst;

                        for (i = 0; i < num_rules; i++) {
                                nfp_flower_compile_ipv4_udp_tun((void *)key,
                                                                (void *)msk, rules[i]);
                        }
                        dst = ((struct nfp_flower_ipv4_udp_tun *)key)->ipv4.dst;

                        /* Store the tunnel destination in the rule data.
                         * This must be present and be an exact match.
                         */
                        flow_pay->nfp_tun_ipv4_addr = dst;
                        nfp_tunnel_add_ipv4_off(priv->app, dst);
                }

                if (key_layer.key_layer_two & NFP_FLOWER_LAYER2_GENEVE_OP) {
                        offset = key_map[FLOW_PAY_GENEVE_OPT];
                        key = kdata + offset;
                        msk = mdata + offset;
                        for (i = 0; i < num_rules; i++)
                                nfp_flower_compile_geneve_opt(key, msk, rules[i]);
                }
        }

        /* Merge actions into flow_pay */
        err = nfp_fl_merge_actions_offload(rules, priv, netdev, flow_pay, num_rules);
        if (err)
                goto ct_offload_err;

        /* Use the pointer address as the cookie, but set the last bit to 1.
         * This is to avoid the 'is_merge_flow' check from detecting this as
         * an already merged flow. This works since address alignment means
         * that the last bit for pointer addresses will be 0.
         */
        flow_pay->tc_flower_cookie = ((unsigned long)flow_pay) | 0x1;
        err = nfp_compile_flow_metadata(priv->app, flow_pay->tc_flower_cookie,
                                        flow_pay, netdev, NULL);
        if (err)
                goto ct_offload_err;

        if (nfp_netdev_is_nfp_repr(netdev))
                port = nfp_port_from_netdev(netdev);

        err = rhashtable_insert_fast(&priv->flow_table, &flow_pay->fl_node,
                                     nfp_flower_table_params);
        if (err)
                goto ct_release_offload_meta_err;

        err = nfp_flower_xmit_flow(priv->app, flow_pay,
                                   NFP_FLOWER_CMSG_TYPE_FLOW_ADD);
        if (err)
                goto ct_remove_rhash_err;

        m_entry->tc_flower_cookie = flow_pay->tc_flower_cookie;
        m_entry->flow_pay = flow_pay;

        if (port)
                port->tc_offload_cnt++;

        return err;

ct_remove_rhash_err:
        WARN_ON_ONCE(rhashtable_remove_fast(&priv->flow_table,
                                            &flow_pay->fl_node,
                                            nfp_flower_table_params));
ct_release_offload_meta_err:
        nfp_modify_flow_metadata(priv->app, flow_pay);
ct_offload_err:
        if (flow_pay->nfp_tun_ipv4_addr)
                nfp_tunnel_del_ipv4_off(priv->app, flow_pay->nfp_tun_ipv4_addr);
        if (flow_pay->nfp_tun_ipv6)
                nfp_tunnel_put_ipv6_off(priv->app, flow_pay->nfp_tun_ipv6);
        kfree(flow_pay->action_data);
        kfree(flow_pay->mask_data);
        kfree(flow_pay->unmasked_data);
        kfree(flow_pay);
        return err;
}

static int nfp_fl_ct_del_offload(struct nfp_app *app, unsigned long cookie,
                                 struct net_device *netdev)
{
        struct nfp_flower_priv *priv = app->priv;
        struct nfp_fl_payload *flow_pay;
        struct nfp_port *port = NULL;
        int err = 0;

        if (nfp_netdev_is_nfp_repr(netdev))
                port = nfp_port_from_netdev(netdev);

        flow_pay = nfp_flower_search_fl_table(app, cookie, netdev);
        if (!flow_pay)
                return -ENOENT;

        err = nfp_modify_flow_metadata(app, flow_pay);
        if (err)
                goto err_free_merge_flow;

        if (flow_pay->nfp_tun_ipv4_addr)
                nfp_tunnel_del_ipv4_off(app, flow_pay->nfp_tun_ipv4_addr);

        if (flow_pay->nfp_tun_ipv6)
                nfp_tunnel_put_ipv6_off(app, flow_pay->nfp_tun_ipv6);

        if (!flow_pay->in_hw) {
                err = 0;
                goto err_free_merge_flow;
        }

        err = nfp_flower_xmit_flow(app, flow_pay,
                                   NFP_FLOWER_CMSG_TYPE_FLOW_DEL);

err_free_merge_flow:
        nfp_flower_del_linked_merge_flows(app, flow_pay);
        if (port)
                port->tc_offload_cnt--;
        kfree(flow_pay->action_data);
        kfree(flow_pay->mask_data);
        kfree(flow_pay->unmasked_data);
        WARN_ON_ONCE(rhashtable_remove_fast(&priv->flow_table,
                                            &flow_pay->fl_node,
                                            nfp_flower_table_params));
        kfree_rcu(flow_pay, rcu);
        return err;
}

static int nfp_ct_do_nft_merge(struct nfp_fl_ct_zone_entry *zt,
                               struct nfp_fl_ct_flow_entry *nft_entry,
                               struct nfp_fl_ct_tc_merge *tc_m_entry)
{
        struct nfp_fl_ct_flow_entry *post_ct_entry, *pre_ct_entry;
        struct nfp_fl_nft_tc_merge *nft_m_entry;
        unsigned long new_cookie[3];
        int err;

        pre_ct_entry = tc_m_entry->pre_ct_parent;
        post_ct_entry = tc_m_entry->post_ct_parent;

        err = nfp_ct_merge_act_check(pre_ct_entry, post_ct_entry, nft_entry);
        if (err)
                return err;

        /* Check that the two tc flows are also compatible with
         * the nft entry. No need to check the pre_ct and post_ct
         * entries as that was already done during pre_merge.
         * The nft entry does not have a chain populated, so
         * skip this check.
         */
        err = nfp_ct_merge_check(pre_ct_entry, nft_entry);
        if (err)
                return err;
        err = nfp_ct_merge_check(nft_entry, post_ct_entry);
        if (err)
                return err;
        err = nfp_ct_check_meta(post_ct_entry, nft_entry);
        if (err)
                return err;

        if (pre_ct_entry->num_prev_m_entries > 0) {
                err = nfp_ct_merge_extra_check(nft_entry, tc_m_entry);
                if (err)
                        return err;
        }

        /* Combine tc_merge and nft cookies for this cookie. */
        new_cookie[0] = tc_m_entry->cookie[0];
        new_cookie[1] = tc_m_entry->cookie[1];
        new_cookie[2] = nft_entry->cookie;
        nft_m_entry = get_hashentry(&zt->nft_merge_tb,
                                    &new_cookie,
                                    nfp_nft_ct_merge_params,
                                    sizeof(*nft_m_entry));

        if (IS_ERR(nft_m_entry))
                return PTR_ERR(nft_m_entry);

        /* nft_m_entry already present, not merging again */
        if (!memcmp(&new_cookie, nft_m_entry->cookie, sizeof(new_cookie)))
                return 0;

        memcpy(&nft_m_entry->cookie, &new_cookie, sizeof(new_cookie));
        nft_m_entry->zt = zt;
        nft_m_entry->tc_m_parent = tc_m_entry;
        nft_m_entry->nft_parent = nft_entry;
        nft_m_entry->tc_flower_cookie = 0;
        /* Copy the netdev from the pre_ct entry. When the tc_m_entry was created
         * it only combined them if the netdevs were the same, so can use any of them.
         */
        nft_m_entry->netdev = pre_ct_entry->netdev;

        /* Add this entry to the tc_m_list and nft_flow lists */
        list_add(&nft_m_entry->tc_merge_list, &tc_m_entry->children);
        list_add(&nft_m_entry->nft_flow_list, &nft_entry->children);

        err = rhashtable_insert_fast(&zt->nft_merge_tb, &nft_m_entry->hash_node,
                                     nfp_nft_ct_merge_params);
        if (err)
                goto err_nft_ct_merge_insert;

        zt->nft_merge_count++;

        if (post_ct_entry->goto_chain_index > 0)
                return nfp_fl_create_new_pre_ct(nft_m_entry);

        /* Generate offload structure and send to nfp */
        err = nfp_fl_ct_add_offload(nft_m_entry);
        if (err)
                goto err_nft_ct_offload;

        return err;

err_nft_ct_offload:
        nfp_fl_ct_del_offload(zt->priv->app, nft_m_entry->tc_flower_cookie,
                              nft_m_entry->netdev);
err_nft_ct_merge_insert:
        list_del(&nft_m_entry->tc_merge_list);
        list_del(&nft_m_entry->nft_flow_list);
        kfree(nft_m_entry);
        return err;
}

static int nfp_ct_do_tc_merge(struct nfp_fl_ct_zone_entry *zt,
                              struct nfp_fl_ct_flow_entry *ct_entry1,
                              struct nfp_fl_ct_flow_entry *ct_entry2)
{
        struct nfp_fl_ct_flow_entry *post_ct_entry, *pre_ct_entry;
        struct nfp_fl_ct_flow_entry *nft_entry, *nft_tmp;
        struct nfp_fl_ct_tc_merge *m_entry;
        unsigned long new_cookie[2];
        int err;

        if (ct_entry1->type == CT_TYPE_PRE_CT) {
                pre_ct_entry = ct_entry1;
                post_ct_entry = ct_entry2;
        } else {
                post_ct_entry = ct_entry1;
                pre_ct_entry = ct_entry2;
        }

        /* Checks that the chain_index of the filter matches the
         * chain_index of the GOTO action.
         */
        if (post_ct_entry->chain_index != pre_ct_entry->goto_chain_index)
                return -EINVAL;

        err = nfp_ct_merge_check(pre_ct_entry, post_ct_entry);
        if (err)
                return err;

        new_cookie[0] = pre_ct_entry->cookie;
        new_cookie[1] = post_ct_entry->cookie;
        m_entry = get_hashentry(&zt->tc_merge_tb, &new_cookie,
                                nfp_tc_ct_merge_params, sizeof(*m_entry));
        if (IS_ERR(m_entry))
                return PTR_ERR(m_entry);

        /* m_entry already present, not merging again */
        if (!memcmp(&new_cookie, m_entry->cookie, sizeof(new_cookie)))
                return 0;

        memcpy(&m_entry->cookie, &new_cookie, sizeof(new_cookie));
        m_entry->zt = zt;
        m_entry->post_ct_parent = post_ct_entry;
        m_entry->pre_ct_parent = pre_ct_entry;

        /* Add this entry to the pre_ct and post_ct lists */
        list_add(&m_entry->post_ct_list, &post_ct_entry->children);
        list_add(&m_entry->pre_ct_list, &pre_ct_entry->children);
        INIT_LIST_HEAD(&m_entry->children);

        err = rhashtable_insert_fast(&zt->tc_merge_tb, &m_entry->hash_node,
                                     nfp_tc_ct_merge_params);
        if (err)
                goto err_ct_tc_merge_insert;
        zt->tc_merge_count++;

        /* Merge with existing nft flows */
        list_for_each_entry_safe(nft_entry, nft_tmp, &zt->nft_flows_list,
                                 list_node) {
                nfp_ct_do_nft_merge(zt, nft_entry, m_entry);
        }

        return 0;

err_ct_tc_merge_insert:
        list_del(&m_entry->post_ct_list);
        list_del(&m_entry->pre_ct_list);
        kfree(m_entry);
        return err;
}

static struct
nfp_fl_ct_zone_entry *get_nfp_zone_entry(struct nfp_flower_priv *priv,
                                         u16 zone, bool wildcarded)
{
        struct nfp_fl_ct_zone_entry *zt;
        int err;

        if (wildcarded && priv->ct_zone_wc)
                return priv->ct_zone_wc;

        if (!wildcarded) {
                zt = get_hashentry(&priv->ct_zone_table, &zone,
                                   nfp_zone_table_params, sizeof(*zt));

                /* If priv is set this is an existing entry, just return it */
                if (IS_ERR(zt) || zt->priv)
                        return zt;
        } else {
                zt = kzalloc_obj(*zt);
                if (!zt)
                        return ERR_PTR(-ENOMEM);
        }

        zt->zone = zone;
        zt->priv = priv;
        zt->nft = NULL;

        /* init the various hash tables and lists */
        INIT_LIST_HEAD(&zt->pre_ct_list);
        INIT_LIST_HEAD(&zt->post_ct_list);
        INIT_LIST_HEAD(&zt->nft_flows_list);

        err = rhashtable_init(&zt->tc_merge_tb, &nfp_tc_ct_merge_params);
        if (err)
                goto err_tc_merge_tb_init;

        err = rhashtable_init(&zt->nft_merge_tb, &nfp_nft_ct_merge_params);
        if (err)
                goto err_nft_merge_tb_init;

        if (wildcarded) {
                priv->ct_zone_wc = zt;
        } else {
                err = rhashtable_insert_fast(&priv->ct_zone_table,
                                             &zt->hash_node,
                                             nfp_zone_table_params);
                if (err)
                        goto err_zone_insert;
        }

        return zt;

err_zone_insert:
        rhashtable_destroy(&zt->nft_merge_tb);
err_nft_merge_tb_init:
        rhashtable_destroy(&zt->tc_merge_tb);
err_tc_merge_tb_init:
        kfree(zt);
        return ERR_PTR(err);
}

static struct net_device *get_netdev_from_rule(struct flow_rule *rule)
{
        if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META)) {
                struct flow_match_meta match;

                flow_rule_match_meta(rule, &match);
                if (match.key->ingress_ifindex & match.mask->ingress_ifindex)
                        return __dev_get_by_index(&init_net,
                                                  match.key->ingress_ifindex);
        }

        return NULL;
}

static void nfp_nft_ct_translate_mangle_action(struct flow_action_entry *mangle_action)
{
        if (mangle_action->id != FLOW_ACTION_MANGLE)
                return;

        switch (mangle_action->mangle.htype) {
        case FLOW_ACT_MANGLE_HDR_TYPE_IP4:
        case FLOW_ACT_MANGLE_HDR_TYPE_IP6:
                mangle_action->mangle.val = (__force u32)cpu_to_be32(mangle_action->mangle.val);
                mangle_action->mangle.mask = (__force u32)cpu_to_be32(mangle_action->mangle.mask);
                return;

        /* Both struct tcphdr and struct udphdr start with
         *      __be16 source;
         *      __be16 dest;
         * so we can use the same code for both.
         */
        case FLOW_ACT_MANGLE_HDR_TYPE_TCP:
        case FLOW_ACT_MANGLE_HDR_TYPE_UDP:
                if (mangle_action->mangle.offset == offsetof(struct tcphdr, source)) {
                        mangle_action->mangle.val =
                                (__force u32)cpu_to_be32(mangle_action->mangle.val << 16);
                        /* The mask of mangle action is inverse mask,
                         * so clear the dest tp port with 0xFFFF to
                         * instead of rotate-left operation.
                         */
                        mangle_action->mangle.mask =
                                (__force u32)cpu_to_be32(mangle_action->mangle.mask << 16 | 0xFFFF);
                }
                if (mangle_action->mangle.offset == offsetof(struct tcphdr, dest)) {
                        mangle_action->mangle.offset = 0;
                        mangle_action->mangle.val =
                                (__force u32)cpu_to_be32(mangle_action->mangle.val);
                        mangle_action->mangle.mask =
                                (__force u32)cpu_to_be32(mangle_action->mangle.mask);
                }
                return;

        default:
                return;
        }
}

static int nfp_nft_ct_set_flow_flag(struct flow_action_entry *act,
                                    struct nfp_fl_ct_flow_entry *entry)
{
        switch (act->id) {
        case FLOW_ACTION_CT:
                if (act->ct.action == TCA_CT_ACT_NAT)
                        entry->flags |= NFP_FL_ACTION_DO_NAT;
                break;

        case FLOW_ACTION_MANGLE:
                entry->flags |= NFP_FL_ACTION_DO_MANGLE;
                break;

        default:
                break;
        }

        return 0;
}

static struct
nfp_fl_ct_flow_entry *nfp_fl_ct_add_flow(struct nfp_fl_ct_zone_entry *zt,
                                         struct net_device *netdev,
                                         struct flow_cls_offload *flow,
                                         bool is_nft, struct netlink_ext_ack *extack)
{
        struct nf_flow_match *nft_match = NULL;
        struct nfp_fl_ct_flow_entry *entry;
        struct nfp_fl_ct_map_entry *map;
        struct flow_action_entry *act;
        int err, i;

        entry = kzalloc_obj(*entry);
        if (!entry)
                return ERR_PTR(-ENOMEM);

        entry->rule = flow_rule_alloc(flow->rule->action.num_entries);
        if (!entry->rule) {
                err = -ENOMEM;
                goto err_pre_ct_rule;
        }

        /* nft flows gets destroyed after callback return, so need
         * to do a full copy instead of just a reference.
         */
        if (is_nft) {
                nft_match = kzalloc_obj(*nft_match);
                if (!nft_match) {
                        err = -ENOMEM;
                        goto err_pre_ct_act;
                }
                memcpy(&nft_match->dissector, flow->rule->match.dissector,
                       sizeof(nft_match->dissector));
                memcpy(&nft_match->mask, flow->rule->match.mask,
                       sizeof(nft_match->mask));
                memcpy(&nft_match->key, flow->rule->match.key,
                       sizeof(nft_match->key));
                entry->rule->match.dissector = &nft_match->dissector;
                entry->rule->match.mask = &nft_match->mask;
                entry->rule->match.key = &nft_match->key;

                if (!netdev)
                        netdev = get_netdev_from_rule(entry->rule);
        } else {
                entry->rule->match.dissector = flow->rule->match.dissector;
                entry->rule->match.mask = flow->rule->match.mask;
                entry->rule->match.key = flow->rule->match.key;
        }

        entry->zt = zt;
        entry->netdev = netdev;
        entry->cookie = flow->cookie > 0 ? flow->cookie : (unsigned long)entry;
        entry->chain_index = flow->common.chain_index;
        entry->tun_offset = NFP_FL_CT_NO_TUN;

        /* Copy over action data. Unfortunately we do not get a handle to the
         * original tcf_action data, and the flow objects gets destroyed, so we
         * cannot just save a pointer to this either, so need to copy over the
         * data unfortunately.
         */
        entry->rule->action.num_entries = flow->rule->action.num_entries;
        flow_action_for_each(i, act, &flow->rule->action) {
                struct flow_action_entry *new_act;

                new_act = &entry->rule->action.entries[i];
                memcpy(new_act, act, sizeof(struct flow_action_entry));
                /* nft entry mangle field is host byte order, need translate to
                 * network byte order.
                 */
                if (is_nft)
                        nfp_nft_ct_translate_mangle_action(new_act);

                nfp_nft_ct_set_flow_flag(new_act, entry);
                /* Entunnel is a special case, need to allocate and copy
                 * tunnel info.
                 */
                if (act->id == FLOW_ACTION_TUNNEL_ENCAP) {
                        struct ip_tunnel_info *tun = act->tunnel;
                        size_t tun_size = sizeof(*tun) + tun->options_len;

                        new_act->tunnel = kmemdup(tun, tun_size, GFP_ATOMIC);
                        if (!new_act->tunnel) {
                                err = -ENOMEM;
                                goto err_pre_ct_tun_cp;
                        }
                        entry->tun_offset = i;
                }
        }

        INIT_LIST_HEAD(&entry->children);

        if (flow->cookie == 0)
                return entry;

        /* Now add a ct map entry to flower-priv */
        map = get_hashentry(&zt->priv->ct_map_table, &flow->cookie,
                            nfp_ct_map_params, sizeof(*map));
        if (IS_ERR(map)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "offload error: ct map entry creation failed");
                err = -ENOMEM;
                goto err_ct_flow_insert;
        }
        map->cookie = flow->cookie;
        map->ct_entry = entry;
        err = rhashtable_insert_fast(&zt->priv->ct_map_table,
                                     &map->hash_node,
                                     nfp_ct_map_params);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "offload error: ct map entry table add failed");
                goto err_map_insert;
        }

        return entry;

err_map_insert:
        kfree(map);
err_ct_flow_insert:
        if (entry->tun_offset != NFP_FL_CT_NO_TUN)
                kfree(entry->rule->action.entries[entry->tun_offset].tunnel);
err_pre_ct_tun_cp:
        kfree(nft_match);
err_pre_ct_act:
        kfree(entry->rule);
err_pre_ct_rule:
        kfree(entry);
        return ERR_PTR(err);
}

static void cleanup_nft_merge_entry(struct nfp_fl_nft_tc_merge *m_entry)
{
        struct nfp_fl_ct_zone_entry *zt;
        int err;

        zt = m_entry->zt;

        /* Flow is in HW, need to delete */
        if (m_entry->tc_flower_cookie) {
                err = nfp_fl_ct_del_offload(zt->priv->app, m_entry->tc_flower_cookie,
                                            m_entry->netdev);
                if (err)
                        return;
        }

        WARN_ON_ONCE(rhashtable_remove_fast(&zt->nft_merge_tb,
                                            &m_entry->hash_node,
                                            nfp_nft_ct_merge_params));
        zt->nft_merge_count--;
        list_del(&m_entry->tc_merge_list);
        list_del(&m_entry->nft_flow_list);

        if (m_entry->next_pre_ct_entry) {
                struct nfp_fl_ct_map_entry pre_ct_map_ent;

                pre_ct_map_ent.ct_entry = m_entry->next_pre_ct_entry;
                pre_ct_map_ent.cookie = 0;
                nfp_fl_ct_del_flow(&pre_ct_map_ent);
        }

        kfree(m_entry);
}

static void nfp_free_nft_merge_children(void *entry, bool is_nft_flow)
{
        struct nfp_fl_nft_tc_merge *m_entry, *tmp;

        /* These post entries are parts of two lists, one is a list of nft_entries
         * and the other is of from a list of tc_merge structures. Iterate
         * through the relevant list and cleanup the entries.
         */

        if (is_nft_flow) {
                /* Need to iterate through list of nft_flow entries */
                struct nfp_fl_ct_flow_entry *ct_entry = entry;

                list_for_each_entry_safe(m_entry, tmp, &ct_entry->children,
                                         nft_flow_list) {
                        cleanup_nft_merge_entry(m_entry);
                }
        } else {
                /* Need to iterate through list of tc_merged_flow entries */
                struct nfp_fl_ct_tc_merge *ct_entry = entry;

                list_for_each_entry_safe(m_entry, tmp, &ct_entry->children,
                                         tc_merge_list) {
                        cleanup_nft_merge_entry(m_entry);
                }
        }
}

static void nfp_del_tc_merge_entry(struct nfp_fl_ct_tc_merge *m_ent)
{
        struct nfp_fl_ct_zone_entry *zt;
        int err;

        zt = m_ent->zt;
        err = rhashtable_remove_fast(&zt->tc_merge_tb,
                                     &m_ent->hash_node,
                                     nfp_tc_ct_merge_params);
        if (err)
                pr_warn("WARNING: could not remove merge_entry from hashtable\n");
        zt->tc_merge_count--;
        list_del(&m_ent->post_ct_list);
        list_del(&m_ent->pre_ct_list);

        if (!list_empty(&m_ent->children))
                nfp_free_nft_merge_children(m_ent, false);
        kfree(m_ent);
}

static void nfp_free_tc_merge_children(struct nfp_fl_ct_flow_entry *entry)
{
        struct nfp_fl_ct_tc_merge *m_ent, *tmp;

        switch (entry->type) {
        case CT_TYPE_PRE_CT:
                list_for_each_entry_safe(m_ent, tmp, &entry->children, pre_ct_list) {
                        nfp_del_tc_merge_entry(m_ent);
                }
                break;
        case CT_TYPE_POST_CT:
                list_for_each_entry_safe(m_ent, tmp, &entry->children, post_ct_list) {
                        nfp_del_tc_merge_entry(m_ent);
                }
                break;
        default:
                break;
        }
}

void nfp_fl_ct_clean_flow_entry(struct nfp_fl_ct_flow_entry *entry)
{
        list_del(&entry->list_node);

        if (!list_empty(&entry->children)) {
                if (entry->type == CT_TYPE_NFT)
                        nfp_free_nft_merge_children(entry, true);
                else
                        nfp_free_tc_merge_children(entry);
        }

        if (entry->tun_offset != NFP_FL_CT_NO_TUN)
                kfree(entry->rule->action.entries[entry->tun_offset].tunnel);

        if (entry->type == CT_TYPE_NFT) {
                struct nf_flow_match *nft_match;

                nft_match = container_of(entry->rule->match.dissector,
                                         struct nf_flow_match, dissector);
                kfree(nft_match);
        }

        kfree(entry->rule);
        kfree(entry);
}

static struct flow_action_entry *get_flow_act_ct(struct flow_rule *rule)
{
        struct flow_action_entry *act;
        int i;

        /* More than one ct action may be present in a flow rule,
         * Return the first one that is not a CT clear action
         */
        flow_action_for_each(i, act, &rule->action) {
                if (act->id == FLOW_ACTION_CT && act->ct.action != TCA_CT_ACT_CLEAR)
                        return act;
        }

        return NULL;
}

static struct flow_action_entry *get_flow_act(struct flow_rule *rule,
                                              enum flow_action_id act_id)
{
        struct flow_action_entry *act = NULL;
        int i;

        flow_action_for_each(i, act, &rule->action) {
                if (act->id == act_id)
                        return act;
        }
        return NULL;
}

static void
nfp_ct_merge_tc_entries(struct nfp_fl_ct_flow_entry *ct_entry1,
                        struct nfp_fl_ct_zone_entry *zt_src,
                        struct nfp_fl_ct_zone_entry *zt_dst)
{
        struct nfp_fl_ct_flow_entry *ct_entry2, *ct_tmp;
        struct list_head *ct_list;

        if (ct_entry1->type == CT_TYPE_PRE_CT)
                ct_list = &zt_src->post_ct_list;
        else if (ct_entry1->type == CT_TYPE_POST_CT)
                ct_list = &zt_src->pre_ct_list;
        else
                return;

        list_for_each_entry_safe(ct_entry2, ct_tmp, ct_list,
                                 list_node) {
                nfp_ct_do_tc_merge(zt_dst, ct_entry2, ct_entry1);
        }
}

static void
nfp_ct_merge_nft_with_tc(struct nfp_fl_ct_flow_entry *nft_entry,
                         struct nfp_fl_ct_zone_entry *zt)
{
        struct nfp_fl_ct_tc_merge *tc_merge_entry;
        struct rhashtable_iter iter;

        rhashtable_walk_enter(&zt->tc_merge_tb, &iter);
        rhashtable_walk_start(&iter);
        while ((tc_merge_entry = rhashtable_walk_next(&iter)) != NULL) {
                if (IS_ERR(tc_merge_entry))
                        continue;
                rhashtable_walk_stop(&iter);
                nfp_ct_do_nft_merge(zt, nft_entry, tc_merge_entry);
                rhashtable_walk_start(&iter);
        }
        rhashtable_walk_stop(&iter);
        rhashtable_walk_exit(&iter);
}

int nfp_fl_ct_handle_pre_ct(struct nfp_flower_priv *priv,
                            struct net_device *netdev,
                            struct flow_cls_offload *flow,
                            struct netlink_ext_ack *extack,
                            struct nfp_fl_nft_tc_merge *m_entry)
{
        struct flow_action_entry *ct_act, *ct_goto;
        struct nfp_fl_ct_flow_entry *ct_entry;
        struct nfp_fl_ct_zone_entry *zt;
        int err;

        ct_act = get_flow_act_ct(flow->rule);
        if (!ct_act) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "unsupported offload: Conntrack action empty in conntrack offload");
                return -EOPNOTSUPP;
        }

        ct_goto = get_flow_act(flow->rule, FLOW_ACTION_GOTO);
        if (!ct_goto) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "unsupported offload: Conntrack requires ACTION_GOTO");
                return -EOPNOTSUPP;
        }

        zt = get_nfp_zone_entry(priv, ct_act->ct.zone, false);
        if (IS_ERR(zt)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "offload error: Could not create zone table entry");
                return PTR_ERR(zt);
        }

        if (!zt->nft) {
                zt->nft = ct_act->ct.flow_table;
                err = nf_flow_table_offload_add_cb(zt->nft, nfp_fl_ct_handle_nft_flow, zt);
                if (err) {
                        NL_SET_ERR_MSG_MOD(extack,
                                           "offload error: Could not register nft_callback");
                        return err;
                }
        }

        /* Add entry to pre_ct_list */
        ct_entry = nfp_fl_ct_add_flow(zt, netdev, flow, false, extack);
        if (IS_ERR(ct_entry))
                return PTR_ERR(ct_entry);
        ct_entry->type = CT_TYPE_PRE_CT;
        ct_entry->chain_index = flow->common.chain_index;
        ct_entry->goto_chain_index = ct_goto->chain_index;

        if (m_entry) {
                struct nfp_fl_ct_flow_entry *pre_ct_entry;
                int i;

                pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent;
                for (i = 0; i < pre_ct_entry->num_prev_m_entries; i++)
                        ct_entry->prev_m_entries[i] = pre_ct_entry->prev_m_entries[i];
                ct_entry->prev_m_entries[i++] = m_entry;
                ct_entry->num_prev_m_entries = i;

                m_entry->next_pre_ct_entry = ct_entry;
        }

        list_add(&ct_entry->list_node, &zt->pre_ct_list);
        zt->pre_ct_count++;

        nfp_ct_merge_tc_entries(ct_entry, zt, zt);

        /* Need to check and merge with tables in the wc_zone as well */
        if (priv->ct_zone_wc)
                nfp_ct_merge_tc_entries(ct_entry, priv->ct_zone_wc, zt);

        return 0;
}

int nfp_fl_ct_handle_post_ct(struct nfp_flower_priv *priv,
                             struct net_device *netdev,
                             struct flow_cls_offload *flow,
                             struct netlink_ext_ack *extack)
{
        struct flow_rule *rule = flow_cls_offload_flow_rule(flow);
        struct nfp_fl_ct_flow_entry *ct_entry;
        struct flow_action_entry *ct_goto;
        struct nfp_fl_ct_zone_entry *zt;
        struct flow_action_entry *act;
        bool wildcarded = false;
        struct flow_match_ct ct;
        int i;

        flow_action_for_each(i, act, &rule->action) {
                switch (act->id) {
                case FLOW_ACTION_REDIRECT:
                case FLOW_ACTION_REDIRECT_INGRESS:
                case FLOW_ACTION_MIRRED:
                case FLOW_ACTION_MIRRED_INGRESS:
                        if (act->dev->rtnl_link_ops &&
                            !strcmp(act->dev->rtnl_link_ops->kind, "openvswitch")) {
                                NL_SET_ERR_MSG_MOD(extack,
                                                   "unsupported offload: out port is openvswitch internal port");
                                return -EOPNOTSUPP;
                        }
                        break;
                default:
                        break;
                }
        }

        flow_rule_match_ct(rule, &ct);
        if (!ct.mask->ct_zone) {
                wildcarded = true;
        } else if (ct.mask->ct_zone != U16_MAX) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "unsupported offload: partially wildcarded ct_zone is not supported");
                return -EOPNOTSUPP;
        }

        zt = get_nfp_zone_entry(priv, ct.key->ct_zone, wildcarded);
        if (IS_ERR(zt)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "offload error: Could not create zone table entry");
                return PTR_ERR(zt);
        }

        /* Add entry to post_ct_list */
        ct_entry = nfp_fl_ct_add_flow(zt, netdev, flow, false, extack);
        if (IS_ERR(ct_entry))
                return PTR_ERR(ct_entry);

        ct_entry->type = CT_TYPE_POST_CT;
        ct_entry->chain_index = flow->common.chain_index;
        ct_goto = get_flow_act(flow->rule, FLOW_ACTION_GOTO);
        ct_entry->goto_chain_index = ct_goto ? ct_goto->chain_index : 0;
        list_add(&ct_entry->list_node, &zt->post_ct_list);
        zt->post_ct_count++;

        if (wildcarded) {
                /* Iterate through all zone tables if not empty, look for merges with
                 * pre_ct entries and merge them.
                 */
                struct rhashtable_iter iter;
                struct nfp_fl_ct_zone_entry *zone_table;

                rhashtable_walk_enter(&priv->ct_zone_table, &iter);
                rhashtable_walk_start(&iter);
                while ((zone_table = rhashtable_walk_next(&iter)) != NULL) {
                        if (IS_ERR(zone_table))
                                continue;
                        rhashtable_walk_stop(&iter);
                        nfp_ct_merge_tc_entries(ct_entry, zone_table, zone_table);
                        rhashtable_walk_start(&iter);
                }
                rhashtable_walk_stop(&iter);
                rhashtable_walk_exit(&iter);
        } else {
                nfp_ct_merge_tc_entries(ct_entry, zt, zt);
        }

        return 0;
}

int nfp_fl_create_new_pre_ct(struct nfp_fl_nft_tc_merge *m_entry)
{
        struct nfp_fl_ct_flow_entry *pre_ct_entry, *post_ct_entry;
        struct flow_cls_offload new_pre_ct_flow;
        int err;

        pre_ct_entry = m_entry->tc_m_parent->pre_ct_parent;
        if (pre_ct_entry->num_prev_m_entries >= NFP_MAX_RECIRC_CT_ZONES - 1)
                return -1;

        post_ct_entry = m_entry->tc_m_parent->post_ct_parent;
        memset(&new_pre_ct_flow, 0, sizeof(struct flow_cls_offload));
        new_pre_ct_flow.rule = post_ct_entry->rule;
        new_pre_ct_flow.common.chain_index = post_ct_entry->chain_index;

        err = nfp_fl_ct_handle_pre_ct(pre_ct_entry->zt->priv,
                                      pre_ct_entry->netdev,
                                      &new_pre_ct_flow, NULL,
                                      m_entry);
        return err;
}

static void
nfp_fl_ct_sub_stats(struct nfp_fl_nft_tc_merge *nft_merge,
                    enum ct_entry_type type, u64 *m_pkts,
                    u64 *m_bytes, u64 *m_used)
{
        struct nfp_flower_priv *priv = nft_merge->zt->priv;
        struct nfp_fl_payload *nfp_flow;
        u32 ctx_id;

        nfp_flow = nft_merge->flow_pay;
        if (!nfp_flow)
                return;

        ctx_id = be32_to_cpu(nfp_flow->meta.host_ctx_id);
        *m_pkts += priv->stats[ctx_id].pkts;
        *m_bytes += priv->stats[ctx_id].bytes;
        *m_used = max_t(u64, *m_used, priv->stats[ctx_id].used);

        /* If request is for a sub_flow which is part of a tunnel merged
         * flow then update stats from tunnel merged flows first.
         */
        if (!list_empty(&nfp_flow->linked_flows))
                nfp_flower_update_merge_stats(priv->app, nfp_flow);

        if (type != CT_TYPE_NFT) {
                /* Update nft cached stats */
                flow_stats_update(&nft_merge->nft_parent->stats,
                                  priv->stats[ctx_id].bytes,
                                  priv->stats[ctx_id].pkts,
                                  0, priv->stats[ctx_id].used,
                                  FLOW_ACTION_HW_STATS_DELAYED);
        } else {
                /* Update pre_ct cached stats */
                flow_stats_update(&nft_merge->tc_m_parent->pre_ct_parent->stats,
                                  priv->stats[ctx_id].bytes,
                                  priv->stats[ctx_id].pkts,
                                  0, priv->stats[ctx_id].used,
                                  FLOW_ACTION_HW_STATS_DELAYED);
                /* Update post_ct cached stats */
                flow_stats_update(&nft_merge->tc_m_parent->post_ct_parent->stats,
                                  priv->stats[ctx_id].bytes,
                                  priv->stats[ctx_id].pkts,
                                  0, priv->stats[ctx_id].used,
                                  FLOW_ACTION_HW_STATS_DELAYED);
        }

        /* Update previous pre_ct/post_ct/nft flow stats */
        if (nft_merge->tc_m_parent->pre_ct_parent->num_prev_m_entries > 0) {
                struct nfp_fl_nft_tc_merge *tmp_nft_merge;
                int i;

                for (i = 0; i < nft_merge->tc_m_parent->pre_ct_parent->num_prev_m_entries; i++) {
                        tmp_nft_merge = nft_merge->tc_m_parent->pre_ct_parent->prev_m_entries[i];
                        flow_stats_update(&tmp_nft_merge->tc_m_parent->pre_ct_parent->stats,
                                          priv->stats[ctx_id].bytes,
                                          priv->stats[ctx_id].pkts,
                                          0, priv->stats[ctx_id].used,
                                          FLOW_ACTION_HW_STATS_DELAYED);
                        flow_stats_update(&tmp_nft_merge->tc_m_parent->post_ct_parent->stats,
                                          priv->stats[ctx_id].bytes,
                                          priv->stats[ctx_id].pkts,
                                          0, priv->stats[ctx_id].used,
                                          FLOW_ACTION_HW_STATS_DELAYED);
                        flow_stats_update(&tmp_nft_merge->nft_parent->stats,
                                          priv->stats[ctx_id].bytes,
                                          priv->stats[ctx_id].pkts,
                                          0, priv->stats[ctx_id].used,
                                          FLOW_ACTION_HW_STATS_DELAYED);
                }
        }

        /* Reset stats from the nfp */
        priv->stats[ctx_id].pkts = 0;
        priv->stats[ctx_id].bytes = 0;
}

int nfp_fl_ct_stats(struct flow_cls_offload *flow,
                    struct nfp_fl_ct_map_entry *ct_map_ent)
{
        struct nfp_fl_ct_flow_entry *ct_entry = ct_map_ent->ct_entry;
        struct nfp_fl_nft_tc_merge *nft_merge, *nft_m_tmp;
        struct nfp_fl_ct_tc_merge *tc_merge, *tc_m_tmp;

        u64 pkts = 0, bytes = 0, used = 0;
        u64 m_pkts, m_bytes, m_used;

        spin_lock_bh(&ct_entry->zt->priv->stats_lock);

        if (ct_entry->type == CT_TYPE_PRE_CT) {
                /* Iterate tc_merge entries associated with this flow */
                list_for_each_entry_safe(tc_merge, tc_m_tmp, &ct_entry->children,
                                         pre_ct_list) {
                        m_pkts = 0;
                        m_bytes = 0;
                        m_used = 0;
                        /* Iterate nft_merge entries associated with this tc_merge flow */
                        list_for_each_entry_safe(nft_merge, nft_m_tmp, &tc_merge->children,
                                                 tc_merge_list) {
                                nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_PRE_CT,
                                                    &m_pkts, &m_bytes, &m_used);
                        }
                        pkts += m_pkts;
                        bytes += m_bytes;
                        used = max_t(u64, used, m_used);
                        /* Update post_ct partner */
                        flow_stats_update(&tc_merge->post_ct_parent->stats,
                                          m_bytes, m_pkts, 0, m_used,
                                          FLOW_ACTION_HW_STATS_DELAYED);
                }
        } else if (ct_entry->type == CT_TYPE_POST_CT) {
                /* Iterate tc_merge entries associated with this flow */
                list_for_each_entry_safe(tc_merge, tc_m_tmp, &ct_entry->children,
                                         post_ct_list) {
                        m_pkts = 0;
                        m_bytes = 0;
                        m_used = 0;
                        /* Iterate nft_merge entries associated with this tc_merge flow */
                        list_for_each_entry_safe(nft_merge, nft_m_tmp, &tc_merge->children,
                                                 tc_merge_list) {
                                nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_POST_CT,
                                                    &m_pkts, &m_bytes, &m_used);
                        }
                        pkts += m_pkts;
                        bytes += m_bytes;
                        used = max_t(u64, used, m_used);
                        /* Update pre_ct partner */
                        flow_stats_update(&tc_merge->pre_ct_parent->stats,
                                          m_bytes, m_pkts, 0, m_used,
                                          FLOW_ACTION_HW_STATS_DELAYED);
                }
        } else  {
                /* Iterate nft_merge entries associated with this nft flow */
                list_for_each_entry_safe(nft_merge, nft_m_tmp, &ct_entry->children,
                                         nft_flow_list) {
                        nfp_fl_ct_sub_stats(nft_merge, CT_TYPE_NFT,
                                            &pkts, &bytes, &used);
                }
        }

        /* Add stats from this request to stats potentially cached by
         * previous requests.
         */
        flow_stats_update(&ct_entry->stats, bytes, pkts, 0, used,
                          FLOW_ACTION_HW_STATS_DELAYED);
        /* Finally update the flow stats from the original stats request */
        flow_stats_update(&flow->stats, ct_entry->stats.bytes,
                          ct_entry->stats.pkts, 0,
                          ct_entry->stats.lastused,
                          FLOW_ACTION_HW_STATS_DELAYED);
        /* Stats has been synced to original flow, can now clear
         * the cache.
         */
        ct_entry->stats.pkts = 0;
        ct_entry->stats.bytes = 0;
        spin_unlock_bh(&ct_entry->zt->priv->stats_lock);

        return 0;
}

static bool
nfp_fl_ct_offload_nft_supported(struct flow_cls_offload *flow)
{
        struct flow_rule *flow_rule = flow->rule;
        struct flow_action *flow_action =
                &flow_rule->action;
        struct flow_action_entry *act;
        int i;

        flow_action_for_each(i, act, flow_action) {
                if (act->id == FLOW_ACTION_CT_METADATA) {
                        enum ip_conntrack_info ctinfo =
                                act->ct_metadata.cookie & NFCT_INFOMASK;

                        return ctinfo != IP_CT_NEW;
                }
        }

        return false;
}

static int
nfp_fl_ct_offload_nft_flow(struct nfp_fl_ct_zone_entry *zt, struct flow_cls_offload *flow)
{
        struct nfp_fl_ct_map_entry *ct_map_ent;
        struct nfp_fl_ct_flow_entry *ct_entry;
        struct netlink_ext_ack *extack = NULL;

        extack = flow->common.extack;
        switch (flow->command) {
        case FLOW_CLS_REPLACE:
                if (!nfp_fl_ct_offload_nft_supported(flow))
                        return -EOPNOTSUPP;

                /* Netfilter can request offload multiple times for the same
                 * flow - protect against adding duplicates.
                 */
                ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie,
                                                    nfp_ct_map_params);
                if (!ct_map_ent) {
                        ct_entry = nfp_fl_ct_add_flow(zt, NULL, flow, true, extack);
                        if (IS_ERR(ct_entry))
                                return PTR_ERR(ct_entry);
                        ct_entry->type = CT_TYPE_NFT;
                        list_add(&ct_entry->list_node, &zt->nft_flows_list);
                        zt->nft_flows_count++;
                        nfp_ct_merge_nft_with_tc(ct_entry, zt);
                }
                return 0;
        case FLOW_CLS_DESTROY:
                ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie,
                                                    nfp_ct_map_params);
                return nfp_fl_ct_del_flow(ct_map_ent);
        case FLOW_CLS_STATS:
                ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table, &flow->cookie,
                                                    nfp_ct_map_params);
                if (ct_map_ent)
                        return nfp_fl_ct_stats(flow, ct_map_ent);
                break;
        default:
                break;
        }
        return -EINVAL;
}

int nfp_fl_ct_handle_nft_flow(enum tc_setup_type type, void *type_data, void *cb_priv)
{
        struct flow_cls_offload *flow = type_data;
        struct nfp_fl_ct_zone_entry *zt = cb_priv;
        int err = -EOPNOTSUPP;

        switch (type) {
        case TC_SETUP_CLSFLOWER:
                while (!mutex_trylock(&zt->priv->nfp_fl_lock)) {
                        if (!zt->nft) /* avoid deadlock */
                                return err;
                        msleep(20);
                }
                err = nfp_fl_ct_offload_nft_flow(zt, flow);
                mutex_unlock(&zt->priv->nfp_fl_lock);
                break;
        default:
                return -EOPNOTSUPP;
        }
        return err;
}

static void
nfp_fl_ct_clean_nft_entries(struct nfp_fl_ct_zone_entry *zt)
{
        struct nfp_fl_ct_flow_entry *nft_entry, *ct_tmp;
        struct nfp_fl_ct_map_entry *ct_map_ent;

        list_for_each_entry_safe(nft_entry, ct_tmp, &zt->nft_flows_list,
                                 list_node) {
                ct_map_ent = rhashtable_lookup_fast(&zt->priv->ct_map_table,
                                                    &nft_entry->cookie,
                                                    nfp_ct_map_params);
                nfp_fl_ct_del_flow(ct_map_ent);
        }
}

int nfp_fl_ct_del_flow(struct nfp_fl_ct_map_entry *ct_map_ent)
{
        struct nfp_fl_ct_flow_entry *ct_entry;
        struct nfp_fl_ct_zone_entry *zt;
        struct rhashtable *m_table;
        struct nf_flowtable *nft;

        if (!ct_map_ent)
                return -ENOENT;

        zt = ct_map_ent->ct_entry->zt;
        ct_entry = ct_map_ent->ct_entry;
        m_table = &zt->priv->ct_map_table;

        switch (ct_entry->type) {
        case CT_TYPE_PRE_CT:
                zt->pre_ct_count--;
                if (ct_map_ent->cookie > 0)
                        rhashtable_remove_fast(m_table, &ct_map_ent->hash_node,
                                               nfp_ct_map_params);
                nfp_fl_ct_clean_flow_entry(ct_entry);
                if (ct_map_ent->cookie > 0)
                        kfree(ct_map_ent);

                if (!zt->pre_ct_count && zt->nft) {
                        nft = zt->nft;
                        zt->nft = NULL; /* avoid deadlock */
                        nf_flow_table_offload_del_cb(nft,
                                                     nfp_fl_ct_handle_nft_flow,
                                                     zt);
                        nfp_fl_ct_clean_nft_entries(zt);
                }
                break;
        case CT_TYPE_POST_CT:
                zt->post_ct_count--;
                rhashtable_remove_fast(m_table, &ct_map_ent->hash_node,
                                       nfp_ct_map_params);
                nfp_fl_ct_clean_flow_entry(ct_entry);
                kfree(ct_map_ent);
                break;
        case CT_TYPE_NFT:
                zt->nft_flows_count--;
                rhashtable_remove_fast(m_table, &ct_map_ent->hash_node,
                                       nfp_ct_map_params);
                nfp_fl_ct_clean_flow_entry(ct_map_ent->ct_entry);
                kfree(ct_map_ent);
                break;
        default:
                break;
        }

        return 0;
}