root/drivers/net/ethernet/mellanox/mlxsw/spectrum2_mr_tcam.c
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2018 Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>

#include "core_acl_flex_actions.h"
#include "spectrum.h"
#include "spectrum_mr.h"

struct mlxsw_sp2_mr_tcam {
        struct mlxsw_sp *mlxsw_sp;
        struct mlxsw_sp_flow_block *flow_block;
        struct mlxsw_sp_acl_ruleset *ruleset4;
        struct mlxsw_sp_acl_ruleset *ruleset6;
};

struct mlxsw_sp2_mr_route {
        struct mlxsw_sp2_mr_tcam *mr_tcam;
};

static struct mlxsw_sp_acl_ruleset *
mlxsw_sp2_mr_tcam_proto_ruleset(struct mlxsw_sp2_mr_tcam *mr_tcam,
                                enum mlxsw_sp_l3proto proto)
{
        switch (proto) {
        case MLXSW_SP_L3_PROTO_IPV4:
                return mr_tcam->ruleset4;
        case MLXSW_SP_L3_PROTO_IPV6:
                return mr_tcam->ruleset6;
        }
        return NULL;
}

static int mlxsw_sp2_mr_tcam_bind_group(struct mlxsw_sp *mlxsw_sp,
                                        enum mlxsw_reg_pemrbt_protocol protocol,
                                        struct mlxsw_sp_acl_ruleset *ruleset)
{
        char pemrbt_pl[MLXSW_REG_PEMRBT_LEN];
        u16 group_id;

        group_id = mlxsw_sp_acl_ruleset_group_id(ruleset);

        mlxsw_reg_pemrbt_pack(pemrbt_pl, protocol, group_id);
        return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pemrbt), pemrbt_pl);
}

static const enum mlxsw_afk_element mlxsw_sp2_mr_tcam_usage_ipv4[] = {
                MLXSW_AFK_ELEMENT_VIRT_ROUTER,
                MLXSW_AFK_ELEMENT_SRC_IP_0_31,
                MLXSW_AFK_ELEMENT_DST_IP_0_31,
};

static int mlxsw_sp2_mr_tcam_ipv4_init(struct mlxsw_sp2_mr_tcam *mr_tcam)
{
        struct mlxsw_afk_element_usage elusage;
        int err;

        /* Initialize IPv4 ACL group. */
        mlxsw_afk_element_usage_fill(&elusage,
                                     mlxsw_sp2_mr_tcam_usage_ipv4,
                                     ARRAY_SIZE(mlxsw_sp2_mr_tcam_usage_ipv4));
        mr_tcam->ruleset4 = mlxsw_sp_acl_ruleset_get(mr_tcam->mlxsw_sp,
                                                     mr_tcam->flow_block,
                                                     MLXSW_SP_L3_PROTO_IPV4,
                                                     MLXSW_SP_ACL_PROFILE_MR,
                                                     &elusage);

        if (IS_ERR(mr_tcam->ruleset4))
                return PTR_ERR(mr_tcam->ruleset4);

        /* MC Router groups should be bound before routes are inserted. */
        err = mlxsw_sp2_mr_tcam_bind_group(mr_tcam->mlxsw_sp,
                                           MLXSW_REG_PEMRBT_PROTO_IPV4,
                                           mr_tcam->ruleset4);
        if (err)
                goto err_bind_group;

        return 0;

err_bind_group:
        mlxsw_sp_acl_ruleset_put(mr_tcam->mlxsw_sp, mr_tcam->ruleset4);
        return err;
}

static void mlxsw_sp2_mr_tcam_ipv4_fini(struct mlxsw_sp2_mr_tcam *mr_tcam)
{
        mlxsw_sp_acl_ruleset_put(mr_tcam->mlxsw_sp, mr_tcam->ruleset4);
}

static const enum mlxsw_afk_element mlxsw_sp2_mr_tcam_usage_ipv6[] = {
                MLXSW_AFK_ELEMENT_VIRT_ROUTER_0_3,
                MLXSW_AFK_ELEMENT_VIRT_ROUTER_4_7,
                MLXSW_AFK_ELEMENT_VIRT_ROUTER_MSB,
                MLXSW_AFK_ELEMENT_SRC_IP_96_127,
                MLXSW_AFK_ELEMENT_SRC_IP_64_95,
                MLXSW_AFK_ELEMENT_SRC_IP_32_63,
                MLXSW_AFK_ELEMENT_SRC_IP_0_31,
                MLXSW_AFK_ELEMENT_DST_IP_96_127,
                MLXSW_AFK_ELEMENT_DST_IP_64_95,
                MLXSW_AFK_ELEMENT_DST_IP_32_63,
                MLXSW_AFK_ELEMENT_DST_IP_0_31,
};

static int mlxsw_sp2_mr_tcam_ipv6_init(struct mlxsw_sp2_mr_tcam *mr_tcam)
{
        struct mlxsw_afk_element_usage elusage;
        int err;

        /* Initialize IPv6 ACL group */
        mlxsw_afk_element_usage_fill(&elusage,
                                     mlxsw_sp2_mr_tcam_usage_ipv6,
                                     ARRAY_SIZE(mlxsw_sp2_mr_tcam_usage_ipv6));
        mr_tcam->ruleset6 = mlxsw_sp_acl_ruleset_get(mr_tcam->mlxsw_sp,
                                                     mr_tcam->flow_block,
                                                     MLXSW_SP_L3_PROTO_IPV6,
                                                     MLXSW_SP_ACL_PROFILE_MR,
                                                     &elusage);

        if (IS_ERR(mr_tcam->ruleset6))
                return PTR_ERR(mr_tcam->ruleset6);

        /* MC Router groups should be bound before routes are inserted. */
        err = mlxsw_sp2_mr_tcam_bind_group(mr_tcam->mlxsw_sp,
                                           MLXSW_REG_PEMRBT_PROTO_IPV6,
                                           mr_tcam->ruleset6);
        if (err)
                goto err_bind_group;

        return 0;

err_bind_group:
        mlxsw_sp_acl_ruleset_put(mr_tcam->mlxsw_sp, mr_tcam->ruleset6);
        return err;
}

static void mlxsw_sp2_mr_tcam_ipv6_fini(struct mlxsw_sp2_mr_tcam *mr_tcam)
{
        mlxsw_sp_acl_ruleset_put(mr_tcam->mlxsw_sp, mr_tcam->ruleset6);
}

static void
mlxsw_sp2_mr_tcam_rule_parse4(struct mlxsw_sp_acl_rule_info *rulei,
                              struct mlxsw_sp_mr_route_key *key)
{
        mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_VIRT_ROUTER,
                                       key->vrid, GENMASK(11, 0));
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP_0_31,
                                       (char *) &key->source.addr4,
                                       (char *) &key->source_mask.addr4, 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP_0_31,
                                       (char *) &key->group.addr4,
                                       (char *) &key->group_mask.addr4, 4);
}

static void
mlxsw_sp2_mr_tcam_rule_parse6(struct mlxsw_sp_acl_rule_info *rulei,
                              struct mlxsw_sp_mr_route_key *key)
{
        mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_VIRT_ROUTER_0_3,
                                       key->vrid, GENMASK(3, 0));
        mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_VIRT_ROUTER_4_7,
                                       key->vrid >> 4, GENMASK(3, 0));
        mlxsw_sp_acl_rulei_keymask_u32(rulei,
                                       MLXSW_AFK_ELEMENT_VIRT_ROUTER_MSB,
                                       key->vrid >> 8, GENMASK(3, 0));
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP_96_127,
                                       &key->source.addr6.s6_addr[0x0],
                                       &key->source_mask.addr6.s6_addr[0x0], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP_64_95,
                                       &key->source.addr6.s6_addr[0x4],
                                       &key->source_mask.addr6.s6_addr[0x4], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP_32_63,
                                       &key->source.addr6.s6_addr[0x8],
                                       &key->source_mask.addr6.s6_addr[0x8], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP_0_31,
                                       &key->source.addr6.s6_addr[0xc],
                                       &key->source_mask.addr6.s6_addr[0xc], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP_96_127,
                                       &key->group.addr6.s6_addr[0x0],
                                       &key->group_mask.addr6.s6_addr[0x0], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP_64_95,
                                       &key->group.addr6.s6_addr[0x4],
                                       &key->group_mask.addr6.s6_addr[0x4], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP_32_63,
                                       &key->group.addr6.s6_addr[0x8],
                                       &key->group_mask.addr6.s6_addr[0x8], 4);
        mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP_0_31,
                                       &key->group.addr6.s6_addr[0xc],
                                       &key->group_mask.addr6.s6_addr[0xc], 4);
}

static void
mlxsw_sp2_mr_tcam_rule_parse(struct mlxsw_sp_acl_rule *rule,
                             struct mlxsw_sp_mr_route_key *key,
                             unsigned int priority)
{
        struct mlxsw_sp_acl_rule_info *rulei;

        rulei = mlxsw_sp_acl_rule_rulei(rule);
        rulei->priority = priority;
        switch (key->proto) {
        case MLXSW_SP_L3_PROTO_IPV4:
                return mlxsw_sp2_mr_tcam_rule_parse4(rulei, key);
        case MLXSW_SP_L3_PROTO_IPV6:
                return mlxsw_sp2_mr_tcam_rule_parse6(rulei, key);
        }
}

static int
mlxsw_sp2_mr_tcam_route_create(struct mlxsw_sp *mlxsw_sp, void *priv,
                               void *route_priv,
                               struct mlxsw_sp_mr_route_key *key,
                               struct mlxsw_afa_block *afa_block,
                               enum mlxsw_sp_mr_route_prio prio)
{
        struct mlxsw_sp2_mr_route *mr_route = route_priv;
        struct mlxsw_sp2_mr_tcam *mr_tcam = priv;
        struct mlxsw_sp_acl_ruleset *ruleset;
        struct mlxsw_sp_acl_rule *rule;
        int err;

        mr_route->mr_tcam = mr_tcam;
        ruleset = mlxsw_sp2_mr_tcam_proto_ruleset(mr_tcam, key->proto);
        if (WARN_ON(!ruleset))
                return -EINVAL;

        rule = mlxsw_sp_acl_rule_create(mlxsw_sp, ruleset,
                                        (unsigned long) route_priv, afa_block,
                                        NULL);
        if (IS_ERR(rule))
                return PTR_ERR(rule);

        mlxsw_sp2_mr_tcam_rule_parse(rule, key, prio);
        err = mlxsw_sp_acl_rule_add(mlxsw_sp, rule);
        if (err)
                goto err_rule_add;

        return 0;

err_rule_add:
        mlxsw_sp_acl_rule_destroy(mlxsw_sp, rule);
        return err;
}

static void
mlxsw_sp2_mr_tcam_route_destroy(struct mlxsw_sp *mlxsw_sp, void *priv,
                                void *route_priv,
                                struct mlxsw_sp_mr_route_key *key)
{
        struct mlxsw_sp2_mr_tcam *mr_tcam = priv;
        struct mlxsw_sp_acl_ruleset *ruleset;
        struct mlxsw_sp_acl_rule *rule;

        ruleset = mlxsw_sp2_mr_tcam_proto_ruleset(mr_tcam, key->proto);
        if (WARN_ON(!ruleset))
                return;

        rule = mlxsw_sp_acl_rule_lookup(mlxsw_sp, ruleset,
                                        (unsigned long) route_priv);
        if (WARN_ON(!rule))
                return;

        mlxsw_sp_acl_rule_del(mlxsw_sp, rule);
        mlxsw_sp_acl_rule_destroy(mlxsw_sp, rule);
}

static int
mlxsw_sp2_mr_tcam_route_update(struct mlxsw_sp *mlxsw_sp,
                               void *route_priv,
                               struct mlxsw_sp_mr_route_key *key,
                               struct mlxsw_afa_block *afa_block)
{
        struct mlxsw_sp2_mr_route *mr_route = route_priv;
        struct mlxsw_sp2_mr_tcam *mr_tcam = mr_route->mr_tcam;
        struct mlxsw_sp_acl_ruleset *ruleset;
        struct mlxsw_sp_acl_rule *rule;

        ruleset = mlxsw_sp2_mr_tcam_proto_ruleset(mr_tcam, key->proto);
        if (WARN_ON(!ruleset))
                return -EINVAL;

        rule = mlxsw_sp_acl_rule_lookup(mlxsw_sp, ruleset,
                                        (unsigned long) route_priv);
        if (WARN_ON(!rule))
                return -EINVAL;

        return mlxsw_sp_acl_rule_action_replace(mlxsw_sp, rule, afa_block);
}

static int mlxsw_sp2_mr_tcam_init(struct mlxsw_sp *mlxsw_sp, void *priv)
{
        struct mlxsw_sp2_mr_tcam *mr_tcam = priv;
        int err;

        mr_tcam->mlxsw_sp = mlxsw_sp;
        mr_tcam->flow_block = mlxsw_sp_flow_block_create(mlxsw_sp, NULL);
        if (!mr_tcam->flow_block)
                return -ENOMEM;

        err = mlxsw_sp2_mr_tcam_ipv4_init(mr_tcam);
        if (err)
                goto err_ipv4_init;

        err = mlxsw_sp2_mr_tcam_ipv6_init(mr_tcam);
        if (err)
                goto err_ipv6_init;

        return 0;

err_ipv6_init:
        mlxsw_sp2_mr_tcam_ipv4_fini(mr_tcam);
err_ipv4_init:
        mlxsw_sp_flow_block_destroy(mr_tcam->flow_block);
        return err;
}

static void mlxsw_sp2_mr_tcam_fini(void *priv)
{
        struct mlxsw_sp2_mr_tcam *mr_tcam = priv;

        mlxsw_sp2_mr_tcam_ipv6_fini(mr_tcam);
        mlxsw_sp2_mr_tcam_ipv4_fini(mr_tcam);
        mlxsw_sp_flow_block_destroy(mr_tcam->flow_block);
}

const struct mlxsw_sp_mr_tcam_ops mlxsw_sp2_mr_tcam_ops = {
        .priv_size = sizeof(struct mlxsw_sp2_mr_tcam),
        .init = mlxsw_sp2_mr_tcam_init,
        .fini = mlxsw_sp2_mr_tcam_fini,
        .route_priv_size = sizeof(struct mlxsw_sp2_mr_route),
        .route_create = mlxsw_sp2_mr_tcam_route_create,
        .route_destroy = mlxsw_sp2_mr_tcam_route_destroy,
        .route_update = mlxsw_sp2_mr_tcam_route_update,
};