summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/microchip/ksz9477_tc_flower.c
blob: 8b2f5be667e01ff37b9408e21de39f20613670c3 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>

#include "ksz9477.h"
#include "ksz9477_reg.h"
#include "ksz_common.h"

#define ETHER_TYPE_FULL_MASK		cpu_to_be16(~0)
#define KSZ9477_MAX_TC			7

/**
 * ksz9477_flower_parse_key_l2 - Parse Layer 2 key from flow rule and configure
 *                               ACL entries accordingly.
 * @dev: Pointer to the ksz_device.
 * @port: Port number.
 * @extack: Pointer to the netlink_ext_ack.
 * @rule: Pointer to the flow_rule.
 * @cookie: The cookie to associate with the entry.
 * @prio: The priority of the entry.
 *
 * This function parses the Layer 2 key from the flow rule and configures
 * the corresponding ACL entries. It checks for unsupported offloads and
 * available entries before proceeding with the configuration.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static int ksz9477_flower_parse_key_l2(struct ksz_device *dev, int port,
				       struct netlink_ext_ack *extack,
				       struct flow_rule *rule,
				       unsigned long cookie, u32 prio)
{
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	struct flow_match_eth_addrs ematch;
	struct ksz9477_acl_entries *acles;
	int required_entries;
	u8 *src_mac = NULL;
	u8 *dst_mac = NULL;
	u16 ethtype = 0;

	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
		struct flow_match_basic match;

		flow_rule_match_basic(rule, &match);

		if (match.key->n_proto) {
			if (match.mask->n_proto != ETHER_TYPE_FULL_MASK) {
				NL_SET_ERR_MSG_MOD(extack,
						   "ethernet type mask must be a full mask");
				return -EINVAL;
			}

			ethtype = be16_to_cpu(match.key->n_proto);
		}
	}

	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
		flow_rule_match_eth_addrs(rule, &ematch);

		if (!is_zero_ether_addr(ematch.key->src)) {
			if (!is_broadcast_ether_addr(ematch.mask->src))
				goto not_full_mask_err;

			src_mac = ematch.key->src;
		}

		if (!is_zero_ether_addr(ematch.key->dst)) {
			if (!is_broadcast_ether_addr(ematch.mask->dst))
				goto not_full_mask_err;

			dst_mac = ematch.key->dst;
		}
	}

	acles = &acl->acles;
	/* ACL supports only one MAC per entry */
	required_entries = src_mac && dst_mac ? 2 : 1;

	/* Check if there are enough available entries */
	if (acles->entries_count + required_entries > KSZ9477_ACL_MAX_ENTRIES) {
		NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
		return -EOPNOTSUPP;
	}

	ksz9477_acl_match_process_l2(dev, port, ethtype, src_mac, dst_mac,
				     cookie, prio);

	return 0;

not_full_mask_err:
	NL_SET_ERR_MSG_MOD(extack, "MAC address mask must be a full mask");
	return -EOPNOTSUPP;
}

/**
 * ksz9477_flower_parse_key - Parse flow rule keys for a specified port on a
 *			      ksz_device.
 * @dev: The ksz_device instance.
 * @port: The port number to parse the flow rule keys for.
 * @extack: The netlink extended ACK for reporting errors.
 * @rule: The flow_rule to parse.
 * @cookie: The cookie to associate with the entry.
 * @prio: The priority of the entry.
 *
 * This function checks if the used keys in the flow rule are supported by
 * the device and parses the L2 keys if they match. If unsupported keys are
 * used, an error message is set in the extended ACK.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static int ksz9477_flower_parse_key(struct ksz_device *dev, int port,
				    struct netlink_ext_ack *extack,
				    struct flow_rule *rule,
				    unsigned long cookie, u32 prio)
{
	struct flow_dissector *dissector = rule->match.dissector;
	int ret;

	if (dissector->used_keys &
	    ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
	      BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
	      BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) {
		NL_SET_ERR_MSG_MOD(extack,
				   "Unsupported keys used");
		return -EOPNOTSUPP;
	}

	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC) ||
	    flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
		ret = ksz9477_flower_parse_key_l2(dev, port, extack, rule,
						  cookie, prio);
		if (ret)
			return ret;
	}

	return 0;
}

/**
 * ksz9477_flower_parse_action - Parse flow rule actions for a specified port
 *				 on a ksz_device.
 * @dev: The ksz_device instance.
 * @port: The port number to parse the flow rule actions for.
 * @extack: The netlink extended ACK for reporting errors.
 * @cls: The flow_cls_offload instance containing the flow rule.
 * @entry_idx: The index of the ACL entry to store the action.
 *
 * This function checks if the actions in the flow rule are supported by
 * the device. Currently, only actions that change priorities are supported.
 * If unsupported actions are encountered, an error message is set in the
 * extended ACK.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
static int ksz9477_flower_parse_action(struct ksz_device *dev, int port,
				       struct netlink_ext_ack *extack,
				       struct flow_cls_offload *cls,
				       int entry_idx)
{
	struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
	const struct flow_action_entry *act;
	struct ksz9477_acl_entry *entry;
	bool prio_force = false;
	u8 prio_val = 0;
	int i;

	if (TC_H_MIN(cls->classid)) {
		NL_SET_ERR_MSG_MOD(extack, "hw_tc is not supported. Use: action skbedit prio");
		return -EOPNOTSUPP;
	}

	flow_action_for_each(i, act, &rule->action) {
		switch (act->id) {
		case FLOW_ACTION_PRIORITY:
			if (act->priority > KSZ9477_MAX_TC) {
				NL_SET_ERR_MSG_MOD(extack, "Priority value is too high");
				return -EOPNOTSUPP;
			}
			prio_force = true;
			prio_val = act->priority;
			break;
		default:
			NL_SET_ERR_MSG_MOD(extack, "action not supported");
			return -EOPNOTSUPP;
		}
	}

	/* pick entry to store action */
	entry = &acl->acles.entries[entry_idx];

	ksz9477_acl_action_rule_cfg(entry->entry, prio_force, prio_val);
	ksz9477_acl_processing_rule_set_action(entry->entry, entry_idx);

	return 0;
}

/**
 * ksz9477_cls_flower_add - Add a flow classification rule for a specified port
 *			    on a ksz_device.
 * @ds: The DSA switch instance.
 * @port: The port number to add the flow classification rule to.
 * @cls: The flow_cls_offload instance containing the flow rule.
 * @ingress: A flag indicating if the rule is applied on the ingress path.
 *
 * This function adds a flow classification rule for a specified port on a
 * ksz_device. It checks if the ACL offloading is supported and parses the flow
 * keys and actions. If the ACL is not supported, it returns an error. If there
 * are unprocessed entries, it parses the action for the rule.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
int ksz9477_cls_flower_add(struct dsa_switch *ds, int port,
			   struct flow_cls_offload *cls, bool ingress)
{
	struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
	struct netlink_ext_ack *extack = cls->common.extack;
	struct ksz_device *dev = ds->priv;
	struct ksz9477_acl_priv *acl;
	int action_entry_idx;
	int ret;

	acl = dev->ports[port].acl_priv;

	if (!acl) {
		NL_SET_ERR_MSG_MOD(extack, "ACL offloading is not supported");
		return -EOPNOTSUPP;
	}

	/* A complex rule set can take multiple entries. Use first entry
	 * to store the action.
	 */
	action_entry_idx = acl->acles.entries_count;

	ret = ksz9477_flower_parse_key(dev, port, extack, rule, cls->cookie,
				       cls->common.prio);
	if (ret)
		return ret;

	ret = ksz9477_flower_parse_action(dev, port, extack, cls,
					  action_entry_idx);
	if (ret)
		return ret;

	ret = ksz9477_sort_acl_entries(dev, port);
	if (ret)
		return ret;

	return ksz9477_acl_write_list(dev, port);
}

/**
 * ksz9477_cls_flower_del - Remove a flow classification rule for a specified
 *			    port on a ksz_device.
 * @ds: The DSA switch instance.
 * @port: The port number to remove the flow classification rule from.
 * @cls: The flow_cls_offload instance containing the flow rule.
 * @ingress: A flag indicating if the rule is applied on the ingress path.
 *
 * This function removes a flow classification rule for a specified port on a
 * ksz_device. It checks if the ACL is initialized, and if not, returns an
 * error. If the ACL is initialized, it removes entries with the specified
 * cookie and rewrites the ACL list.
 *
 * Returns: 0 on success or a negative error code on failure.
 */
int ksz9477_cls_flower_del(struct dsa_switch *ds, int port,
			   struct flow_cls_offload *cls, bool ingress)
{
	unsigned long cookie = cls->cookie;
	struct ksz_device *dev = ds->priv;
	struct ksz9477_acl_priv *acl;

	acl = dev->ports[port].acl_priv;

	if (!acl)
		return -EOPNOTSUPP;

	ksz9477_acl_remove_entries(dev, port, &acl->acles, cookie);

	return ksz9477_acl_write_list(dev, port);
}