// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2020 Marvell International Ltd. All rights reserved */ #include #include #include "prestera.h" #include "prestera_hw.h" #include "prestera_acl.h" #include "prestera_span.h" struct prestera_span_entry { struct list_head list; struct prestera_port *port; refcount_t ref_count; u8 id; }; struct prestera_span { struct prestera_switch *sw; struct list_head entries; }; static struct prestera_span_entry * prestera_span_entry_create(struct prestera_port *port, u8 span_id) { struct prestera_span_entry *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return ERR_PTR(-ENOMEM); refcount_set(&entry->ref_count, 1); entry->port = port; entry->id = span_id; list_add_tail(&entry->list, &port->sw->span->entries); return entry; } static void prestera_span_entry_del(struct prestera_span_entry *entry) { list_del(&entry->list); kfree(entry); } static struct prestera_span_entry * prestera_span_entry_find_by_id(struct prestera_span *span, u8 span_id) { struct prestera_span_entry *entry; list_for_each_entry(entry, &span->entries, list) { if (entry->id == span_id) return entry; } return NULL; } static struct prestera_span_entry * prestera_span_entry_find_by_port(struct prestera_span *span, struct prestera_port *port) { struct prestera_span_entry *entry; list_for_each_entry(entry, &span->entries, list) { if (entry->port == port) return entry; } return NULL; } static int prestera_span_get(struct prestera_port *port, u8 *span_id) { u8 new_span_id; struct prestera_switch *sw = port->sw; struct prestera_span_entry *entry; int err; entry = prestera_span_entry_find_by_port(sw->span, port); if (entry) { refcount_inc(&entry->ref_count); *span_id = entry->id; return 0; } err = prestera_hw_span_get(port, &new_span_id); if (err) return err; entry = prestera_span_entry_create(port, new_span_id); if (IS_ERR(entry)) { prestera_hw_span_release(sw, new_span_id); return PTR_ERR(entry); } *span_id = new_span_id; return 0; } static int prestera_span_put(struct prestera_switch *sw, u8 span_id) { struct prestera_span_entry *entry; int err; entry = prestera_span_entry_find_by_id(sw->span, span_id); if (!entry) return false; if (!refcount_dec_and_test(&entry->ref_count)) return 0; err = prestera_hw_span_release(sw, span_id); if (err) return err; prestera_span_entry_del(entry); return 0; } static int prestera_span_rule_add(struct prestera_flow_block_binding *binding, struct prestera_port *to_port) { struct prestera_switch *sw = binding->port->sw; u8 span_id; int err; if (binding->span_id != PRESTERA_SPAN_INVALID_ID) /* port already in mirroring */ return -EEXIST; err = prestera_span_get(to_port, &span_id); if (err) return err; err = prestera_hw_span_bind(binding->port, span_id); if (err) { prestera_span_put(sw, span_id); return err; } binding->span_id = span_id; return 0; } static int prestera_span_rule_del(struct prestera_flow_block_binding *binding) { int err; err = prestera_hw_span_unbind(binding->port); if (err) return err; err = prestera_span_put(binding->port->sw, binding->span_id); if (err) return err; binding->span_id = PRESTERA_SPAN_INVALID_ID; return 0; } int prestera_span_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; port = netdev_priv(act->dev); list_for_each_entry(binding, &block->binding_list, list) { err = prestera_span_rule_add(binding, port); if (err) goto rollback; } return 0; rollback: list_for_each_entry_continue_reverse(binding, &block->binding_list, list) prestera_span_rule_del(binding); return err; } void prestera_span_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); } int prestera_span_init(struct prestera_switch *sw) { struct prestera_span *span; span = kzalloc(sizeof(*span), GFP_KERNEL); if (!span) return -ENOMEM; INIT_LIST_HEAD(&span->entries); sw->span = span; span->sw = sw; return 0; } void prestera_span_fini(struct prestera_switch *sw) { struct prestera_span *span = sw->span; WARN_ON(!list_empty(&span->entries)); kfree(span); }