mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-02 03:44:36 +08:00
13defa275e
- Introduce matchall filter support - Add SPAN API to configure port mirroring. - Add tc mirror action. At this moment, only mirror (egress) action is supported. Example: tc filter ... action mirred egress mirror dev DEV Co-developed-by: Volodymyr Mytnyk <vmytnyk@marvell.com> Signed-off-by: Volodymyr Mytnyk <vmytnyk@marvell.com> Signed-off-by: Serhiy Boiko <serhiy.boiko@plvision.eu> Signed-off-by: Vadym Kochan <vkochan@marvell.com> Signed-off-by: David S. Miller <davem@davemloft.net>
240 lines
5.0 KiB
C
240 lines
5.0 KiB
C
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
|
/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
|
|
#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);
|
|
}
|