331 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			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_8_10,
 | |
| 		MLXSW_AFK_ELEMENT_VIRT_ROUTER_0_7,
 | |
| 		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_8_10,
 | |
| 		MLXSW_AFK_ELEMENT_VIRT_ROUTER_0_7,
 | |
| 		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_0_7,
 | |
| 				       key->vrid, GENMASK(7, 0));
 | |
| 	mlxsw_sp_acl_rulei_keymask_u32(rulei,
 | |
| 				       MLXSW_AFK_ELEMENT_VIRT_ROUTER_8_10,
 | |
| 				       key->vrid >> 8, GENMASK(2, 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,
 | |
| };
 |