// 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_MSB,
		MLXSW_AFK_ELEMENT_VIRT_ROUTER_LSB,
		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_MSB,
		MLXSW_AFK_ELEMENT_VIRT_ROUTER_LSB,
		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_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_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;
	mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_VIRT_ROUTER_LSB,
				       key->vrid, GENMASK(7, 0));
	mlxsw_sp_acl_rulei_keymask_u32(rulei,
				       MLXSW_AFK_ELEMENT_VIRT_ROUTER_MSB,
				       key->vrid >> 8, GENMASK(3, 0));
	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,
};
