spectrum_matchall.c 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2017-2020 Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <net/flow_offload.h>

#include "spectrum.h"
#include "spectrum_span.h"
#include "reg.h"

enum mlxsw_sp_mall_action_type {
	MLXSW_SP_MALL_ACTION_TYPE_MIRROR,
	MLXSW_SP_MALL_ACTION_TYPE_SAMPLE,
};

struct mlxsw_sp_mall_mirror_entry {
19
	const struct net_device *to_dev;
20 21 22 23 24 25 26 27 28 29
	int span_id;
	bool ingress;
};

struct mlxsw_sp_mall_entry {
	struct list_head list;
	unsigned long cookie;
	enum mlxsw_sp_mall_action_type type;
	union {
		struct mlxsw_sp_mall_mirror_entry mirror;
30
		struct mlxsw_sp_port_sample sample;
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
	};
};

static struct mlxsw_sp_mall_entry *
mlxsw_sp_mall_entry_find(struct mlxsw_sp_port *port, unsigned long cookie)
{
	struct mlxsw_sp_mall_entry *mall_entry;

	list_for_each_entry(mall_entry, &port->mall_list, list)
		if (mall_entry->cookie == cookie)
			return mall_entry;

	return NULL;
}

static int
mlxsw_sp_mall_port_mirror_add(struct mlxsw_sp_port *mlxsw_sp_port,
48
			      struct mlxsw_sp_mall_entry *mall_entry,
49 50 51 52
			      bool ingress)
{
	enum mlxsw_sp_span_type span_type;

53
	if (!mall_entry->mirror.to_dev) {
54 55 56 57
		netdev_err(mlxsw_sp_port->dev, "Could not find requested device\n");
		return -EINVAL;
	}

58 59 60 61 62 63 64
	mall_entry->mirror.ingress = ingress;
	span_type = mall_entry->mirror.ingress ? MLXSW_SP_SPAN_INGRESS :
						 MLXSW_SP_SPAN_EGRESS;
	return mlxsw_sp_span_mirror_add(mlxsw_sp_port,
					mall_entry->mirror.to_dev,
					span_type, true,
					&mall_entry->mirror.span_id);
65 66 67 68
}

static void
mlxsw_sp_mall_port_mirror_del(struct mlxsw_sp_port *mlxsw_sp_port,
69
			      struct mlxsw_sp_mall_entry *mall_entry)
70 71 72
{
	enum mlxsw_sp_span_type span_type;

73 74 75
	span_type = mall_entry->mirror.ingress ? MLXSW_SP_SPAN_INGRESS :
						 MLXSW_SP_SPAN_EGRESS;
	mlxsw_sp_span_mirror_del(mlxsw_sp_port, mall_entry->mirror.span_id,
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
				 span_type, true);
}

static int mlxsw_sp_mall_port_sample_set(struct mlxsw_sp_port *mlxsw_sp_port,
					 bool enable, u32 rate)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
	char mpsc_pl[MLXSW_REG_MPSC_LEN];

	mlxsw_reg_mpsc_pack(mpsc_pl, mlxsw_sp_port->local_port, enable, rate);
	return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mpsc), mpsc_pl);
}

static int
mlxsw_sp_mall_port_sample_add(struct mlxsw_sp_port *mlxsw_sp_port,
91
			      struct mlxsw_sp_mall_entry *mall_entry)
92 93 94 95 96 97 98 99 100 101
{
	int err;

	if (!mlxsw_sp_port->sample)
		return -EOPNOTSUPP;
	if (rtnl_dereference(mlxsw_sp_port->sample->psample_group)) {
		netdev_err(mlxsw_sp_port->dev, "sample already active\n");
		return -EEXIST;
	}
	rcu_assign_pointer(mlxsw_sp_port->sample->psample_group,
102 103 104 105
			   mall_entry->sample.psample_group);
	mlxsw_sp_port->sample->truncate = mall_entry->sample.truncate;
	mlxsw_sp_port->sample->trunc_size = mall_entry->sample.trunc_size;
	mlxsw_sp_port->sample->rate = mall_entry->sample.rate;
106 107

	err = mlxsw_sp_mall_port_sample_set(mlxsw_sp_port, true,
108
					    mall_entry->sample.rate);
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
	if (err)
		goto err_port_sample_set;
	return 0;

err_port_sample_set:
	RCU_INIT_POINTER(mlxsw_sp_port->sample->psample_group, NULL);
	return err;
}

static void
mlxsw_sp_mall_port_sample_del(struct mlxsw_sp_port *mlxsw_sp_port)
{
	if (!mlxsw_sp_port->sample)
		return;

	mlxsw_sp_mall_port_sample_set(mlxsw_sp_port, false, 1);
	RCU_INIT_POINTER(mlxsw_sp_port->sample->psample_group, NULL);
}

int mlxsw_sp_mall_replace(struct mlxsw_sp_port *mlxsw_sp_port,
			  struct tc_cls_matchall_offload *f, bool ingress)
{
	struct mlxsw_sp_mall_entry *mall_entry;
	__be16 protocol = f->common.protocol;
	struct flow_action_entry *act;
	int err;

	if (!flow_offload_has_one_action(&f->rule->action)) {
		netdev_err(mlxsw_sp_port->dev, "only singular actions are supported\n");
		return -EOPNOTSUPP;
	}

	mall_entry = kzalloc(sizeof(*mall_entry), GFP_KERNEL);
	if (!mall_entry)
		return -ENOMEM;
	mall_entry->cookie = f->cookie;

	act = &f->rule->action.entries[0];

	if (act->id == FLOW_ACTION_MIRRED && protocol == htons(ETH_P_ALL)) {
		mall_entry->type = MLXSW_SP_MALL_ACTION_TYPE_MIRROR;
150 151
		mall_entry->mirror.to_dev = act->dev;
		err = mlxsw_sp_mall_port_mirror_add(mlxsw_sp_port, mall_entry,
152 153 154
						    ingress);
	} else if (act->id == FLOW_ACTION_SAMPLE &&
		   protocol == htons(ETH_P_ALL)) {
155 156 157 158 159
		if (act->sample.rate > MLXSW_REG_MPSC_RATE_MAX) {
			netdev_err(mlxsw_sp_port->dev, "sample rate not supported\n");
			err = -EOPNOTSUPP;
			goto errout;
		}
160
		mall_entry->type = MLXSW_SP_MALL_ACTION_TYPE_SAMPLE;
161 162 163 164 165
		mall_entry->sample.psample_group = act->sample.psample_group;
		mall_entry->sample.truncate = act->sample.truncate;
		mall_entry->sample.trunc_size = act->sample.trunc_size;
		mall_entry->sample.rate = act->sample.rate;
		err = mlxsw_sp_mall_port_sample_add(mlxsw_sp_port, mall_entry);
166 167 168 169 170
	} else {
		err = -EOPNOTSUPP;
	}

	if (err)
171
		goto errout;
172 173 174 175

	list_add_tail(&mall_entry->list, &mlxsw_sp_port->mall_list);
	return 0;

176
errout:
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	kfree(mall_entry);
	return err;
}

void mlxsw_sp_mall_destroy(struct mlxsw_sp_port *mlxsw_sp_port,
			   struct tc_cls_matchall_offload *f)
{
	struct mlxsw_sp_mall_entry *mall_entry;

	mall_entry = mlxsw_sp_mall_entry_find(mlxsw_sp_port, f->cookie);
	if (!mall_entry) {
		netdev_dbg(mlxsw_sp_port->dev, "tc entry not found on port\n");
		return;
	}
	list_del(&mall_entry->list);

	switch (mall_entry->type) {
	case MLXSW_SP_MALL_ACTION_TYPE_MIRROR:
195
		mlxsw_sp_mall_port_mirror_del(mlxsw_sp_port, mall_entry);
196 197 198 199 200 201 202 203 204 205
		break;
	case MLXSW_SP_MALL_ACTION_TYPE_SAMPLE:
		mlxsw_sp_mall_port_sample_del(mlxsw_sp_port);
		break;
	default:
		WARN_ON(1);
	}

	kfree(mall_entry);
}