summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/marvell/prestera/prestera_matchall.c
blob: 6f2b95a5263ecab73f0aef4a61acb7476a8269c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// 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)
			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;
}