root/drivers/net/ethernet/microchip/lan966x/lan966x_vcap_impl.c
// SPDX-License-Identifier: GPL-2.0+

#include "lan966x_main.h"
#include "lan966x_vcap_ag_api.h"
#include "vcap_api.h"
#include "vcap_api_client.h"
#include "vcap_api_debugfs.h"

#define STREAMSIZE (64 * 4)

#define LAN966X_IS1_LOOKUPS 3
#define LAN966X_IS2_LOOKUPS 2
#define LAN966X_ES0_LOOKUPS 1

#define LAN966X_STAT_ESDX_GRN_BYTES 0x300
#define LAN966X_STAT_ESDX_GRN_PKTS 0x301
#define LAN966X_STAT_ESDX_YEL_BYTES 0x302
#define LAN966X_STAT_ESDX_YEL_PKTS 0x303

static struct lan966x_vcap_inst {
        enum vcap_type vtype; /* type of vcap */
        int tgt_inst; /* hardware instance number */
        int lookups; /* number of lookups in this vcap type */
        int first_cid; /* first chain id in this vcap */
        int last_cid; /* last chain id in this vcap */
        int count; /* number of available addresses */
        bool ingress; /* is vcap in the ingress path */
} lan966x_vcap_inst_cfg[] = {
        {
                .vtype = VCAP_TYPE_ES0,
                .tgt_inst = 0,
                .lookups = LAN966X_ES0_LOOKUPS,
                .first_cid = LAN966X_VCAP_CID_ES0_L0,
                .last_cid = LAN966X_VCAP_CID_ES0_MAX,
                .count = 64,
        },
        {
                .vtype = VCAP_TYPE_IS1, /* IS1-0 */
                .tgt_inst = 1,
                .lookups = LAN966X_IS1_LOOKUPS,
                .first_cid = LAN966X_VCAP_CID_IS1_L0,
                .last_cid = LAN966X_VCAP_CID_IS1_MAX,
                .count = 768,
                .ingress = true,
        },
        {
                .vtype = VCAP_TYPE_IS2, /* IS2-0 */
                .tgt_inst = 2,
                .lookups = LAN966X_IS2_LOOKUPS,
                .first_cid = LAN966X_VCAP_CID_IS2_L0,
                .last_cid = LAN966X_VCAP_CID_IS2_MAX,
                .count = 256,
                .ingress = true,
        },
};

struct lan966x_vcap_cmd_cb {
        struct lan966x *lan966x;
        u32 instance;
};

static u32 lan966x_vcap_read_update_ctrl(const struct lan966x_vcap_cmd_cb *cb)
{
        return lan_rd(cb->lan966x, VCAP_UPDATE_CTRL(cb->instance));
}

static void lan966x_vcap_wait_update(struct lan966x *lan966x, int instance)
{
        const struct lan966x_vcap_cmd_cb cb = { .lan966x = lan966x,
                                                .instance = instance };
        u32 val;

        readx_poll_timeout(lan966x_vcap_read_update_ctrl, &cb, val,
                           (val & VCAP_UPDATE_CTRL_UPDATE_SHOT) == 0, 10,
                           100000);
}

static void __lan966x_vcap_range_init(struct lan966x *lan966x,
                                      struct vcap_admin *admin,
                                      u32 addr,
                                      u32 count)
{
        lan_wr(VCAP_MV_CFG_MV_NUM_POS_SET(0) |
               VCAP_MV_CFG_MV_SIZE_SET(count - 1),
               lan966x, VCAP_MV_CFG(admin->tgt_inst));

        lan_wr(VCAP_UPDATE_CTRL_UPDATE_CMD_SET(VCAP_CMD_INITIALIZE) |
               VCAP_UPDATE_CTRL_UPDATE_ENTRY_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_ACTION_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_CNT_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_ADDR_SET(addr) |
               VCAP_UPDATE_CTRL_CLEAR_CACHE_SET(true) |
               VCAP_UPDATE_CTRL_UPDATE_SHOT_SET(1),
               lan966x, VCAP_UPDATE_CTRL(admin->tgt_inst));

        lan966x_vcap_wait_update(lan966x, admin->tgt_inst);
}

static int lan966x_vcap_is1_cid_to_lookup(int cid)
{
        int lookup = 0;

        if (cid >= LAN966X_VCAP_CID_IS1_L1 &&
            cid < LAN966X_VCAP_CID_IS1_L2)
                lookup = 1;
        else if (cid >= LAN966X_VCAP_CID_IS1_L2 &&
                 cid < LAN966X_VCAP_CID_IS1_MAX)
                lookup = 2;

        return lookup;
}

static int lan966x_vcap_is2_cid_to_lookup(int cid)
{
        if (cid >= LAN966X_VCAP_CID_IS2_L1 &&
            cid < LAN966X_VCAP_CID_IS2_MAX)
                return 1;

        return 0;
}

/* Return the list of keysets for the vcap port configuration */
static int
lan966x_vcap_is1_get_port_keysets(struct net_device *ndev, int lookup,
                                  struct vcap_keyset_list *keysetlist,
                                  u16 l3_proto)
{
        struct lan966x_port *port = netdev_priv(ndev);
        struct lan966x *lan966x = port->lan966x;
        u32 val;

        val = lan_rd(lan966x, ANA_VCAP_S1_CFG(port->chip_port, lookup));

        /* Collect all keysets for the port in a list */
        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_IP) {
                switch (ANA_VCAP_S1_CFG_KEY_IP4_CFG_GET(val)) {
                case VCAP_IS1_PS_IPV4_7TUPLE:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_7TUPLE);
                        break;
                case VCAP_IS1_PS_IPV4_5TUPLE_IP4:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_5TUPLE_IP4);
                        break;
                case VCAP_IS1_PS_IPV4_NORMAL:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_NORMAL);
                        break;
                }
        }

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_IPV6) {
                switch (ANA_VCAP_S1_CFG_KEY_IP6_CFG_GET(val)) {
                case VCAP_IS1_PS_IPV6_NORMAL:
                case VCAP_IS1_PS_IPV6_NORMAL_IP6:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_NORMAL);
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_NORMAL_IP6);
                        break;
                case VCAP_IS1_PS_IPV6_5TUPLE_IP6:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_5TUPLE_IP6);
                        break;
                case VCAP_IS1_PS_IPV6_7TUPLE:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_7TUPLE);
                        break;
                case VCAP_IS1_PS_IPV6_5TUPLE_IP4:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_5TUPLE_IP4);
                        break;
                case VCAP_IS1_PS_IPV6_DMAC_VID:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_DMAC_VID);
                        break;
                }
        }

        switch (ANA_VCAP_S1_CFG_KEY_OTHER_CFG_GET(val)) {
        case VCAP_IS1_PS_OTHER_7TUPLE:
                vcap_keyset_list_add(keysetlist, VCAP_KFS_7TUPLE);
                break;
        case VCAP_IS1_PS_OTHER_NORMAL:
                vcap_keyset_list_add(keysetlist, VCAP_KFS_NORMAL);
                break;
        }

        return 0;
}

static int
lan966x_vcap_is2_get_port_keysets(struct net_device *dev, int lookup,
                                  struct vcap_keyset_list *keysetlist,
                                  u16 l3_proto)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        bool found = false;
        u32 val;

        val = lan_rd(lan966x, ANA_VCAP_S2_CFG(port->chip_port));

        /* Collect all keysets for the port in a list */
        if (l3_proto == ETH_P_ALL)
                vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_SNAP) {
                if (ANA_VCAP_S2_CFG_SNAP_DIS_GET(val) & (BIT(0) << lookup))
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_LLC);
                else
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_SNAP);

                found = true;
        }

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_CFM) {
                if (ANA_VCAP_S2_CFG_OAM_DIS_GET(val) & (BIT(0) << lookup))
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);
                else
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_OAM);

                found = true;
        }

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_ARP) {
                if (ANA_VCAP_S2_CFG_ARP_DIS_GET(val) & (BIT(0) << lookup))
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);
                else
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_ARP);

                found = true;
        }

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_IP) {
                if (ANA_VCAP_S2_CFG_IP_OTHER_DIS_GET(val) & (BIT(0) << lookup))
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);
                else
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP4_OTHER);

                if (ANA_VCAP_S2_CFG_IP_TCPUDP_DIS_GET(val) & (BIT(0) << lookup))
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);
                else
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP4_TCP_UDP);

                found = true;
        }

        if (l3_proto == ETH_P_ALL || l3_proto == ETH_P_IPV6) {
                switch (ANA_VCAP_S2_CFG_IP6_CFG_GET(val) & (0x3 << lookup)) {
                case VCAP_IS2_PS_IPV6_TCPUDP_OTHER:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP6_OTHER);
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP6_TCP_UDP);
                        break;
                case VCAP_IS2_PS_IPV6_STD:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP6_STD);
                        break;
                case VCAP_IS2_PS_IPV6_IP4_TCPUDP_IP4_OTHER:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP4_OTHER);
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_IP4_TCP_UDP);
                        break;
                case VCAP_IS2_PS_IPV6_MAC_ETYPE:
                        vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);
                        break;
                }

                found = true;
        }

        if (!found)
                vcap_keyset_list_add(keysetlist, VCAP_KFS_MAC_ETYPE);

        return 0;
}

static enum vcap_keyfield_set
lan966x_vcap_validate_keyset(struct net_device *dev,
                             struct vcap_admin *admin,
                             struct vcap_rule *rule,
                             struct vcap_keyset_list *kslist,
                             u16 l3_proto)
{
        struct vcap_keyset_list keysetlist = {};
        enum vcap_keyfield_set keysets[10] = {};
        int lookup;
        int err;

        if (!kslist || kslist->cnt == 0)
                return VCAP_KFS_NO_VALUE;

        keysetlist.max = ARRAY_SIZE(keysets);
        keysetlist.keysets = keysets;

        switch (admin->vtype) {
        case VCAP_TYPE_IS1:
                lookup = lan966x_vcap_is1_cid_to_lookup(rule->vcap_chain_id);
                err = lan966x_vcap_is1_get_port_keysets(dev, lookup, &keysetlist,
                                                        l3_proto);
                break;
        case VCAP_TYPE_IS2:
                lookup = lan966x_vcap_is2_cid_to_lookup(rule->vcap_chain_id);
                err = lan966x_vcap_is2_get_port_keysets(dev, lookup, &keysetlist,
                                                        l3_proto);
                break;
        case VCAP_TYPE_ES0:
                return kslist->keysets[0];
        default:
                pr_err("vcap type: %s not supported\n",
                       lan966x_vcaps[admin->vtype].name);
                return VCAP_KFS_NO_VALUE;
        }

        if (err)
                return VCAP_KFS_NO_VALUE;

        /* Check if there is a match and return the match */
        for (int i = 0; i < kslist->cnt; ++i)
                for (int j = 0; j < keysetlist.cnt; ++j)
                        if (kslist->keysets[i] == keysets[j])
                                return kslist->keysets[i];

        return VCAP_KFS_NO_VALUE;
}

static bool lan966x_vcap_is2_is_first_chain(struct vcap_rule *rule)
{
        return (rule->vcap_chain_id >= LAN966X_VCAP_CID_IS2_L0 &&
                rule->vcap_chain_id < LAN966X_VCAP_CID_IS2_L1);
}

static void lan966x_vcap_is1_add_default_fields(struct lan966x_port *port,
                                                struct vcap_admin *admin,
                                                struct vcap_rule *rule)
{
        u32 value, mask;
        u32 lookup;

        if (vcap_rule_get_key_u32(rule, VCAP_KF_IF_IGR_PORT_MASK,
                                  &value, &mask))
                vcap_rule_add_key_u32(rule, VCAP_KF_IF_IGR_PORT_MASK, 0,
                                      ~BIT(port->chip_port));

        lookup = lan966x_vcap_is1_cid_to_lookup(rule->vcap_chain_id);
        vcap_rule_add_key_u32(rule, VCAP_KF_LOOKUP_INDEX, lookup, 0x3);
}

static void lan966x_vcap_is2_add_default_fields(struct lan966x_port *port,
                                                struct vcap_admin *admin,
                                                struct vcap_rule *rule)
{
        u32 value, mask;

        if (vcap_rule_get_key_u32(rule, VCAP_KF_IF_IGR_PORT_MASK,
                                  &value, &mask))
                vcap_rule_add_key_u32(rule, VCAP_KF_IF_IGR_PORT_MASK, 0,
                                      ~BIT(port->chip_port));

        if (lan966x_vcap_is2_is_first_chain(rule))
                vcap_rule_add_key_bit(rule, VCAP_KF_LOOKUP_FIRST_IS,
                                      VCAP_BIT_1);
        else
                vcap_rule_add_key_bit(rule, VCAP_KF_LOOKUP_FIRST_IS,
                                      VCAP_BIT_0);
}

static void lan966x_vcap_es0_add_default_fields(struct lan966x_port *port,
                                                struct vcap_admin *admin,
                                                struct vcap_rule *rule)
{
        vcap_rule_add_key_u32(rule, VCAP_KF_IF_EGR_PORT_NO,
                              port->chip_port, GENMASK(4, 0));
}

static void lan966x_vcap_add_default_fields(struct net_device *dev,
                                            struct vcap_admin *admin,
                                            struct vcap_rule *rule)
{
        struct lan966x_port *port = netdev_priv(dev);

        switch (admin->vtype) {
        case VCAP_TYPE_IS1:
                lan966x_vcap_is1_add_default_fields(port, admin, rule);
                break;
        case VCAP_TYPE_IS2:
                lan966x_vcap_is2_add_default_fields(port, admin, rule);
                break;
        case VCAP_TYPE_ES0:
                lan966x_vcap_es0_add_default_fields(port, admin, rule);
                break;
        default:
                pr_err("vcap type: %s not supported\n",
                       lan966x_vcaps[admin->vtype].name);
                break;
        }
}

static void lan966x_vcap_cache_erase(struct vcap_admin *admin)
{
        memset(admin->cache.keystream, 0, STREAMSIZE);
        memset(admin->cache.maskstream, 0, STREAMSIZE);
        memset(admin->cache.actionstream, 0, STREAMSIZE);
        memset(&admin->cache.counter, 0, sizeof(admin->cache.counter));
}

/* The ESDX counter is only used/incremented if the frame has been classified
 * with an ISDX > 0 (e.g by a rule in IS0).  This is not mentioned in the
 * datasheet.
 */
static void lan966x_es0_read_esdx_counter(struct lan966x *lan966x,
                                          struct vcap_admin *admin, u32 id)
{
        u32 counter;

        id = id & 0xff; /* counter limit */
        spin_lock(&lan966x->stats_lock);
        lan_wr(SYS_STAT_CFG_STAT_VIEW_SET(id), lan966x, SYS_STAT_CFG);
        counter = lan_rd(lan966x, SYS_CNT(LAN966X_STAT_ESDX_GRN_PKTS)) +
                  lan_rd(lan966x, SYS_CNT(LAN966X_STAT_ESDX_YEL_PKTS));
        spin_unlock(&lan966x->stats_lock);
        if (counter)
                admin->cache.counter = counter;
}

static void lan966x_es0_write_esdx_counter(struct lan966x *lan966x,
                                           struct vcap_admin *admin, u32 id)
{
        id = id & 0xff; /* counter limit */

        spin_lock(&lan966x->stats_lock);
        lan_wr(SYS_STAT_CFG_STAT_VIEW_SET(id), lan966x, SYS_STAT_CFG);
        lan_wr(0, lan966x, SYS_CNT(LAN966X_STAT_ESDX_GRN_BYTES));
        lan_wr(admin->cache.counter, lan966x,
               SYS_CNT(LAN966X_STAT_ESDX_GRN_PKTS));
        lan_wr(0, lan966x, SYS_CNT(LAN966X_STAT_ESDX_YEL_BYTES));
        lan_wr(0, lan966x, SYS_CNT(LAN966X_STAT_ESDX_YEL_PKTS));
        spin_unlock(&lan966x->stats_lock);
}

static void lan966x_vcap_cache_write(struct net_device *dev,
                                     struct vcap_admin *admin,
                                     enum vcap_selection sel,
                                     u32 start,
                                     u32 count)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        u32 *keystr, *mskstr, *actstr;

        keystr = &admin->cache.keystream[start];
        mskstr = &admin->cache.maskstream[start];
        actstr = &admin->cache.actionstream[start];

        switch (sel) {
        case VCAP_SEL_ENTRY:
                for (int i = 0; i < count; ++i) {
                        lan_wr(keystr[i] & mskstr[i], lan966x,
                               VCAP_ENTRY_DAT(admin->tgt_inst, i));
                        lan_wr(~mskstr[i], lan966x,
                               VCAP_MASK_DAT(admin->tgt_inst, i));
                }
                break;
        case VCAP_SEL_ACTION:
                for (int i = 0; i < count; ++i)
                        lan_wr(actstr[i], lan966x,
                               VCAP_ACTION_DAT(admin->tgt_inst, i));
                break;
        case VCAP_SEL_COUNTER:
                admin->cache.sticky = admin->cache.counter > 0;
                lan_wr(admin->cache.counter, lan966x,
                       VCAP_CNT_DAT(admin->tgt_inst, 0));

                if (admin->vtype == VCAP_TYPE_ES0)
                        lan966x_es0_write_esdx_counter(lan966x, admin, start);
                break;
        default:
                break;
        }
}

static void lan966x_vcap_cache_read(struct net_device *dev,
                                    struct vcap_admin *admin,
                                    enum vcap_selection sel,
                                    u32 start,
                                    u32 count)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        int instance = admin->tgt_inst;
        u32 *keystr, *mskstr, *actstr;

        keystr = &admin->cache.keystream[start];
        mskstr = &admin->cache.maskstream[start];
        actstr = &admin->cache.actionstream[start];

        if (sel & VCAP_SEL_ENTRY) {
                for (int i = 0; i < count; ++i) {
                        keystr[i] =
                                lan_rd(lan966x, VCAP_ENTRY_DAT(instance, i));
                        mskstr[i] =
                                ~lan_rd(lan966x, VCAP_MASK_DAT(instance, i));
                }
        }

        if (sel & VCAP_SEL_ACTION)
                for (int i = 0; i < count; ++i)
                        actstr[i] =
                                lan_rd(lan966x, VCAP_ACTION_DAT(instance, i));

        if (sel & VCAP_SEL_COUNTER) {
                admin->cache.counter =
                        lan_rd(lan966x, VCAP_CNT_DAT(instance, 0));
                admin->cache.sticky = admin->cache.counter > 0;

                if (admin->vtype == VCAP_TYPE_ES0)
                        lan966x_es0_read_esdx_counter(lan966x, admin, start);
        }
}

static void lan966x_vcap_range_init(struct net_device *dev,
                                    struct vcap_admin *admin,
                                    u32 addr,
                                    u32 count)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;

        __lan966x_vcap_range_init(lan966x, admin, addr, count);
}

static void lan966x_vcap_update(struct net_device *dev,
                                struct vcap_admin *admin,
                                enum vcap_command cmd,
                                enum vcap_selection sel,
                                u32 addr)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        bool clear;

        clear = (cmd == VCAP_CMD_INITIALIZE);

        lan_wr(VCAP_MV_CFG_MV_NUM_POS_SET(0) |
               VCAP_MV_CFG_MV_SIZE_SET(0),
               lan966x, VCAP_MV_CFG(admin->tgt_inst));

        lan_wr(VCAP_UPDATE_CTRL_UPDATE_CMD_SET(cmd) |
               VCAP_UPDATE_CTRL_UPDATE_ENTRY_DIS_SET((VCAP_SEL_ENTRY & sel) == 0) |
               VCAP_UPDATE_CTRL_UPDATE_ACTION_DIS_SET((VCAP_SEL_ACTION & sel) == 0) |
               VCAP_UPDATE_CTRL_UPDATE_CNT_DIS_SET((VCAP_SEL_COUNTER & sel) == 0) |
               VCAP_UPDATE_CTRL_UPDATE_ADDR_SET(addr) |
               VCAP_UPDATE_CTRL_CLEAR_CACHE_SET(clear) |
               VCAP_UPDATE_CTRL_UPDATE_SHOT,
               lan966x, VCAP_UPDATE_CTRL(admin->tgt_inst));

        lan966x_vcap_wait_update(lan966x, admin->tgt_inst);
}

static void lan966x_vcap_move(struct net_device *dev,
                              struct vcap_admin *admin,
                              u32 addr, int offset, int count)
{
        struct lan966x_port *port = netdev_priv(dev);
        struct lan966x *lan966x = port->lan966x;
        enum vcap_command cmd;
        u16 mv_num_pos;
        u16 mv_size;

        mv_size = count - 1;
        if (offset > 0) {
                mv_num_pos = offset - 1;
                cmd = VCAP_CMD_MOVE_DOWN;
        } else {
                mv_num_pos = -offset - 1;
                cmd = VCAP_CMD_MOVE_UP;
        }

        lan_wr(VCAP_MV_CFG_MV_NUM_POS_SET(mv_num_pos) |
               VCAP_MV_CFG_MV_SIZE_SET(mv_size),
               lan966x, VCAP_MV_CFG(admin->tgt_inst));

        lan_wr(VCAP_UPDATE_CTRL_UPDATE_CMD_SET(cmd) |
               VCAP_UPDATE_CTRL_UPDATE_ENTRY_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_ACTION_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_CNT_DIS_SET(0) |
               VCAP_UPDATE_CTRL_UPDATE_ADDR_SET(addr) |
               VCAP_UPDATE_CTRL_CLEAR_CACHE_SET(false) |
               VCAP_UPDATE_CTRL_UPDATE_SHOT,
               lan966x, VCAP_UPDATE_CTRL(admin->tgt_inst));

        lan966x_vcap_wait_update(lan966x, admin->tgt_inst);
}

static const struct vcap_operations lan966x_vcap_ops = {
        .validate_keyset = lan966x_vcap_validate_keyset,
        .add_default_fields = lan966x_vcap_add_default_fields,
        .cache_erase = lan966x_vcap_cache_erase,
        .cache_write = lan966x_vcap_cache_write,
        .cache_read = lan966x_vcap_cache_read,
        .init = lan966x_vcap_range_init,
        .update = lan966x_vcap_update,
        .move = lan966x_vcap_move,
        .port_info = lan966x_vcap_port_info,
};

static void lan966x_vcap_admin_free(struct vcap_admin *admin)
{
        if (!admin)
                return;

        kfree(admin->cache.keystream);
        kfree(admin->cache.maskstream);
        kfree(admin->cache.actionstream);
        mutex_destroy(&admin->lock);
        kfree(admin);
}

static struct vcap_admin *
lan966x_vcap_admin_alloc(struct lan966x *lan966x, struct vcap_control *ctrl,
                         const struct lan966x_vcap_inst *cfg)
{
        struct vcap_admin *admin;

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

        mutex_init(&admin->lock);
        INIT_LIST_HEAD(&admin->list);
        INIT_LIST_HEAD(&admin->rules);
        INIT_LIST_HEAD(&admin->enabled);

        admin->vtype = cfg->vtype;
        admin->vinst = 0;
        admin->ingress = cfg->ingress;
        admin->w32be = true;
        admin->tgt_inst = cfg->tgt_inst;

        admin->lookups = cfg->lookups;
        admin->lookups_per_instance = cfg->lookups;

        admin->first_cid = cfg->first_cid;
        admin->last_cid = cfg->last_cid;

        admin->cache.keystream = kzalloc(STREAMSIZE, GFP_KERNEL);
        admin->cache.maskstream = kzalloc(STREAMSIZE, GFP_KERNEL);
        admin->cache.actionstream = kzalloc(STREAMSIZE, GFP_KERNEL);
        if (!admin->cache.keystream ||
            !admin->cache.maskstream ||
            !admin->cache.actionstream) {
                lan966x_vcap_admin_free(admin);
                return ERR_PTR(-ENOMEM);
        }

        return admin;
}

static void lan966x_vcap_block_init(struct lan966x *lan966x,
                                    struct vcap_admin *admin,
                                    struct lan966x_vcap_inst *cfg)
{
        admin->first_valid_addr = 0;
        admin->last_used_addr = cfg->count;
        admin->last_valid_addr = cfg->count - 1;

        lan_wr(VCAP_CORE_IDX_CORE_IDX_SET(0),
               lan966x, VCAP_CORE_IDX(admin->tgt_inst));
        lan_wr(VCAP_CORE_MAP_CORE_MAP_SET(1),
               lan966x, VCAP_CORE_MAP(admin->tgt_inst));

        __lan966x_vcap_range_init(lan966x, admin, admin->first_valid_addr,
                                  admin->last_valid_addr -
                                        admin->first_valid_addr);
}

static void lan966x_vcap_port_key_deselection(struct lan966x *lan966x,
                                              struct vcap_admin *admin)
{
        u32 val;

        switch (admin->vtype) {
        case VCAP_TYPE_IS1:
                val = ANA_VCAP_S1_CFG_KEY_IP6_CFG_SET(VCAP_IS1_PS_IPV6_5TUPLE_IP6) |
                      ANA_VCAP_S1_CFG_KEY_IP4_CFG_SET(VCAP_IS1_PS_IPV4_5TUPLE_IP4) |
                      ANA_VCAP_S1_CFG_KEY_OTHER_CFG_SET(VCAP_IS1_PS_OTHER_NORMAL);

                for (int p = 0; p < lan966x->num_phys_ports; ++p) {
                        if (!lan966x->ports[p])
                                continue;

                        for (int l = 0; l < LAN966X_IS1_LOOKUPS; ++l)
                                lan_wr(val, lan966x, ANA_VCAP_S1_CFG(p, l));

                        lan_rmw(ANA_VCAP_CFG_S1_ENA_SET(true),
                                ANA_VCAP_CFG_S1_ENA, lan966x,
                                ANA_VCAP_CFG(p));
                }

                break;
        case VCAP_TYPE_IS2:
                for (int p = 0; p < lan966x->num_phys_ports; ++p)
                        lan_wr(0, lan966x, ANA_VCAP_S2_CFG(p));

                break;
        case VCAP_TYPE_ES0:
                for (int p = 0; p < lan966x->num_phys_ports; ++p)
                        lan_rmw(REW_PORT_CFG_ES0_EN_SET(false),
                                REW_PORT_CFG_ES0_EN, lan966x,
                                REW_PORT_CFG(p));
                break;
        default:
                pr_err("vcap type: %s not supported\n",
                       lan966x_vcaps[admin->vtype].name);
                break;
        }
}

int lan966x_vcap_init(struct lan966x *lan966x)
{
        struct lan966x_vcap_inst *cfg;
        struct vcap_control *ctrl;
        struct vcap_admin *admin;
        struct dentry *dir;

        ctrl = kzalloc_obj(*ctrl);
        if (!ctrl)
                return -ENOMEM;

        ctrl->vcaps = lan966x_vcaps;
        ctrl->stats = &lan966x_vcap_stats;
        ctrl->ops = &lan966x_vcap_ops;

        INIT_LIST_HEAD(&ctrl->list);
        for (int i = 0; i < ARRAY_SIZE(lan966x_vcap_inst_cfg); ++i) {
                cfg = &lan966x_vcap_inst_cfg[i];

                admin = lan966x_vcap_admin_alloc(lan966x, ctrl, cfg);
                if (IS_ERR(admin))
                        return PTR_ERR(admin);

                lan966x_vcap_block_init(lan966x, admin, cfg);
                lan966x_vcap_port_key_deselection(lan966x, admin);

                list_add_tail(&admin->list, &ctrl->list);
        }

        dir = vcap_debugfs(lan966x->dev, lan966x->debugfs_root, ctrl);
        for (int p = 0; p < lan966x->num_phys_ports; ++p) {
                if (lan966x->ports[p]) {
                        vcap_port_debugfs(lan966x->dev, dir, ctrl,
                                          lan966x->ports[p]->dev);

                        lan_rmw(ANA_VCAP_S2_CFG_ENA_SET(true),
                                ANA_VCAP_S2_CFG_ENA, lan966x,
                                ANA_VCAP_S2_CFG(lan966x->ports[p]->chip_port));

                        lan_rmw(ANA_VCAP_CFG_S1_ENA_SET(true),
                                ANA_VCAP_CFG_S1_ENA, lan966x,
                                ANA_VCAP_CFG(lan966x->ports[p]->chip_port));

                        lan_rmw(REW_PORT_CFG_ES0_EN_SET(true),
                                REW_PORT_CFG_ES0_EN, lan966x,
                                REW_PORT_CFG(lan966x->ports[p]->chip_port));
                }
        }

        /* Statistics: Use ESDX from ES0 if hit, otherwise no counting */
        lan_rmw(REW_STAT_CFG_STAT_MODE_SET(1),
                REW_STAT_CFG_STAT_MODE, lan966x,
                REW_STAT_CFG);

        lan966x->vcap_ctrl = ctrl;

        return 0;
}

void lan966x_vcap_deinit(struct lan966x *lan966x)
{
        struct vcap_admin *admin, *admin_next;
        struct vcap_control *ctrl;

        ctrl = lan966x->vcap_ctrl;
        if (!ctrl)
                return;

        list_for_each_entry_safe(admin, admin_next, &ctrl->list, list) {
                lan966x_vcap_port_key_deselection(lan966x, admin);
                vcap_del_rules(ctrl, admin);
                list_del(&admin->list);
                lan966x_vcap_admin_free(admin);
        }

        kfree(ctrl);
}