| // 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; |
| } |