128 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
 | |
| /* Copyright (c) 2019-2022 Marvell International Ltd. All rights reserved */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/list.h>
 | |
| 
 | |
| #include "prestera.h"
 | |
| #include "prestera_hw.h"
 | |
| #include "prestera_flow.h"
 | |
| #include "prestera_flower.h"
 | |
| #include "prestera_matchall.h"
 | |
| #include "prestera_span.h"
 | |
| 
 | |
| static int prestera_mall_prio_check(struct prestera_flow_block *block,
 | |
| 				    struct tc_cls_matchall_offload *f)
 | |
| {
 | |
| 	u32 flower_prio_min;
 | |
| 	u32 flower_prio_max;
 | |
| 	int err;
 | |
| 
 | |
| 	err = prestera_flower_prio_get(block, f->common.chain_index,
 | |
| 				       &flower_prio_min, &flower_prio_max);
 | |
| 	if (err == -ENOENT)
 | |
| 		/* No flower filters installed on this chain. */
 | |
| 		return 0;
 | |
| 
 | |
| 	if (err) {
 | |
| 		NL_SET_ERR_MSG(f->common.extack, "Failed to get flower priorities");
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	if (f->common.prio <= flower_prio_max && !block->ingress) {
 | |
| 		NL_SET_ERR_MSG(f->common.extack, "Failed to add in front of existing flower rules");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 	if (f->common.prio >= flower_prio_min && block->ingress) {
 | |
| 		NL_SET_ERR_MSG(f->common.extack, "Failed to add behind of existing flower rules");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int prestera_mall_prio_get(struct prestera_flow_block *block,
 | |
| 			   u32 *prio_min, u32 *prio_max)
 | |
| {
 | |
| 	if (!block->mall.bound)
 | |
| 		return -ENOENT;
 | |
| 
 | |
| 	*prio_min = block->mall.prio_min;
 | |
| 	*prio_max = block->mall.prio_max;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void prestera_mall_prio_update(struct prestera_flow_block *block,
 | |
| 				      struct tc_cls_matchall_offload *f)
 | |
| {
 | |
| 	block->mall.prio_min = min(block->mall.prio_min, f->common.prio);
 | |
| 	block->mall.prio_max = max(block->mall.prio_max, f->common.prio);
 | |
| }
 | |
| 
 | |
| int prestera_mall_replace(struct prestera_flow_block *block,
 | |
| 			  struct tc_cls_matchall_offload *f)
 | |
| {
 | |
| 	struct prestera_flow_block_binding *binding;
 | |
| 	__be16 protocol = f->common.protocol;
 | |
| 	struct flow_action_entry *act;
 | |
| 	struct prestera_port *port;
 | |
| 	int err;
 | |
| 
 | |
| 	if (!flow_offload_has_one_action(&f->rule->action)) {
 | |
| 		NL_SET_ERR_MSG(f->common.extack,
 | |
| 			       "Only singular actions are supported");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	act = &f->rule->action.entries[0];
 | |
| 
 | |
| 	if (!prestera_netdev_check(act->dev)) {
 | |
| 		NL_SET_ERR_MSG(f->common.extack,
 | |
| 			       "Only Marvell Prestera port is supported");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	if (!tc_cls_can_offload_and_chain0(act->dev, &f->common))
 | |
| 		return -EOPNOTSUPP;
 | |
| 	if (act->id != FLOW_ACTION_MIRRED)
 | |
| 		return -EOPNOTSUPP;
 | |
| 	if (protocol != htons(ETH_P_ALL))
 | |
| 		return -EOPNOTSUPP;
 | |
| 
 | |
| 	err = prestera_mall_prio_check(block, f);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	port = netdev_priv(act->dev);
 | |
| 
 | |
| 	list_for_each_entry(binding, &block->binding_list, list) {
 | |
| 		err = prestera_span_rule_add(binding, port, block->ingress);
 | |
| 		if (err == -EEXIST)
 | |
| 			return err;
 | |
| 		if (err)
 | |
| 			goto rollback;
 | |
| 	}
 | |
| 
 | |
| 	prestera_mall_prio_update(block, f);
 | |
| 
 | |
| 	block->mall.bound = true;
 | |
| 	return 0;
 | |
| 
 | |
| rollback:
 | |
| 	list_for_each_entry_continue_reverse(binding,
 | |
| 					     &block->binding_list, list)
 | |
| 		prestera_span_rule_del(binding, block->ingress);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void prestera_mall_destroy(struct prestera_flow_block *block)
 | |
| {
 | |
| 	struct prestera_flow_block_binding *binding;
 | |
| 
 | |
| 	list_for_each_entry(binding, &block->binding_list, list)
 | |
| 		prestera_span_rule_del(binding, block->ingress);
 | |
| 
 | |
| 	block->mall.prio_min = UINT_MAX;
 | |
| 	block->mall.prio_max = 0;
 | |
| 	block->mall.bound = false;
 | |
| }
 |