xref: /linux/drivers/net/dsa/microchip/ksz9477_tc_flower.c (revision 621cde16e49b3ecf7d59a8106a20aaebfb4a59a9)
1002841beSOleksij Rempel // SPDX-License-Identifier: GPL-2.0
2002841beSOleksij Rempel // Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
3002841beSOleksij Rempel 
4002841beSOleksij Rempel #include "ksz9477.h"
5002841beSOleksij Rempel #include "ksz9477_reg.h"
6002841beSOleksij Rempel #include "ksz_common.h"
7002841beSOleksij Rempel 
8002841beSOleksij Rempel #define ETHER_TYPE_FULL_MASK		cpu_to_be16(~0)
9002841beSOleksij Rempel #define KSZ9477_MAX_TC			7
10002841beSOleksij Rempel 
11002841beSOleksij Rempel /**
12002841beSOleksij Rempel  * ksz9477_flower_parse_key_l2 - Parse Layer 2 key from flow rule and configure
13002841beSOleksij Rempel  *                               ACL entries accordingly.
14002841beSOleksij Rempel  * @dev: Pointer to the ksz_device.
15002841beSOleksij Rempel  * @port: Port number.
16002841beSOleksij Rempel  * @extack: Pointer to the netlink_ext_ack.
17002841beSOleksij Rempel  * @rule: Pointer to the flow_rule.
18002841beSOleksij Rempel  * @cookie: The cookie to associate with the entry.
19002841beSOleksij Rempel  * @prio: The priority of the entry.
20002841beSOleksij Rempel  *
21002841beSOleksij Rempel  * This function parses the Layer 2 key from the flow rule and configures
22002841beSOleksij Rempel  * the corresponding ACL entries. It checks for unsupported offloads and
23002841beSOleksij Rempel  * available entries before proceeding with the configuration.
24002841beSOleksij Rempel  *
25002841beSOleksij Rempel  * Returns: 0 on success or a negative error code on failure.
26002841beSOleksij Rempel  */
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)27002841beSOleksij Rempel static int ksz9477_flower_parse_key_l2(struct ksz_device *dev, int port,
28002841beSOleksij Rempel 				       struct netlink_ext_ack *extack,
29002841beSOleksij Rempel 				       struct flow_rule *rule,
30002841beSOleksij Rempel 				       unsigned long cookie, u32 prio)
31002841beSOleksij Rempel {
32002841beSOleksij Rempel 	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
33002841beSOleksij Rempel 	struct flow_match_eth_addrs ematch;
34002841beSOleksij Rempel 	struct ksz9477_acl_entries *acles;
35002841beSOleksij Rempel 	int required_entries;
36002841beSOleksij Rempel 	u8 *src_mac = NULL;
37002841beSOleksij Rempel 	u8 *dst_mac = NULL;
38002841beSOleksij Rempel 	u16 ethtype = 0;
39002841beSOleksij Rempel 
40002841beSOleksij Rempel 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
41002841beSOleksij Rempel 		struct flow_match_basic match;
42002841beSOleksij Rempel 
43002841beSOleksij Rempel 		flow_rule_match_basic(rule, &match);
44002841beSOleksij Rempel 
45002841beSOleksij Rempel 		if (match.key->n_proto) {
46002841beSOleksij Rempel 			if (match.mask->n_proto != ETHER_TYPE_FULL_MASK) {
47002841beSOleksij Rempel 				NL_SET_ERR_MSG_MOD(extack,
48002841beSOleksij Rempel 						   "ethernet type mask must be a full mask");
49002841beSOleksij Rempel 				return -EINVAL;
50002841beSOleksij Rempel 			}
51002841beSOleksij Rempel 
52002841beSOleksij Rempel 			ethtype = be16_to_cpu(match.key->n_proto);
53002841beSOleksij Rempel 		}
54002841beSOleksij Rempel 	}
55002841beSOleksij Rempel 
56002841beSOleksij Rempel 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
57002841beSOleksij Rempel 		flow_rule_match_eth_addrs(rule, &ematch);
58002841beSOleksij Rempel 
59002841beSOleksij Rempel 		if (!is_zero_ether_addr(ematch.key->src)) {
60002841beSOleksij Rempel 			if (!is_broadcast_ether_addr(ematch.mask->src))
61002841beSOleksij Rempel 				goto not_full_mask_err;
62002841beSOleksij Rempel 
63002841beSOleksij Rempel 			src_mac = ematch.key->src;
64002841beSOleksij Rempel 		}
65002841beSOleksij Rempel 
66002841beSOleksij Rempel 		if (!is_zero_ether_addr(ematch.key->dst)) {
67002841beSOleksij Rempel 			if (!is_broadcast_ether_addr(ematch.mask->dst))
68002841beSOleksij Rempel 				goto not_full_mask_err;
69002841beSOleksij Rempel 
70002841beSOleksij Rempel 			dst_mac = ematch.key->dst;
71002841beSOleksij Rempel 		}
72002841beSOleksij Rempel 	}
73002841beSOleksij Rempel 
74002841beSOleksij Rempel 	acles = &acl->acles;
75002841beSOleksij Rempel 	/* ACL supports only one MAC per entry */
76002841beSOleksij Rempel 	required_entries = src_mac && dst_mac ? 2 : 1;
77002841beSOleksij Rempel 
78002841beSOleksij Rempel 	/* Check if there are enough available entries */
79002841beSOleksij Rempel 	if (acles->entries_count + required_entries > KSZ9477_ACL_MAX_ENTRIES) {
80002841beSOleksij Rempel 		NL_SET_ERR_MSG_MOD(extack, "ACL entry limit reached");
81002841beSOleksij Rempel 		return -EOPNOTSUPP;
82002841beSOleksij Rempel 	}
83002841beSOleksij Rempel 
84002841beSOleksij Rempel 	ksz9477_acl_match_process_l2(dev, port, ethtype, src_mac, dst_mac,
85002841beSOleksij Rempel 				     cookie, prio);
86002841beSOleksij Rempel 
87002841beSOleksij Rempel 	return 0;
88002841beSOleksij Rempel 
89002841beSOleksij Rempel not_full_mask_err:
90002841beSOleksij Rempel 	NL_SET_ERR_MSG_MOD(extack, "MAC address mask must be a full mask");
91002841beSOleksij Rempel 	return -EOPNOTSUPP;
92002841beSOleksij Rempel }
93002841beSOleksij Rempel 
94002841beSOleksij Rempel /**
95002841beSOleksij Rempel  * ksz9477_flower_parse_key - Parse flow rule keys for a specified port on a
96002841beSOleksij Rempel  *			      ksz_device.
97002841beSOleksij Rempel  * @dev: The ksz_device instance.
98002841beSOleksij Rempel  * @port: The port number to parse the flow rule keys for.
99002841beSOleksij Rempel  * @extack: The netlink extended ACK for reporting errors.
100002841beSOleksij Rempel  * @rule: The flow_rule to parse.
101002841beSOleksij Rempel  * @cookie: The cookie to associate with the entry.
102002841beSOleksij Rempel  * @prio: The priority of the entry.
103002841beSOleksij Rempel  *
104002841beSOleksij Rempel  * This function checks if the used keys in the flow rule are supported by
105002841beSOleksij Rempel  * the device and parses the L2 keys if they match. If unsupported keys are
106002841beSOleksij Rempel  * used, an error message is set in the extended ACK.
107002841beSOleksij Rempel  *
108002841beSOleksij Rempel  * Returns: 0 on success or a negative error code on failure.
109002841beSOleksij Rempel  */
ksz9477_flower_parse_key(struct ksz_device * dev,int port,struct netlink_ext_ack * extack,struct flow_rule * rule,unsigned long cookie,u32 prio)110002841beSOleksij Rempel static int ksz9477_flower_parse_key(struct ksz_device *dev, int port,
111002841beSOleksij Rempel 				    struct netlink_ext_ack *extack,
112002841beSOleksij Rempel 				    struct flow_rule *rule,
113002841beSOleksij Rempel 				    unsigned long cookie, u32 prio)
114002841beSOleksij Rempel {
115002841beSOleksij Rempel 	struct flow_dissector *dissector = rule->match.dissector;
116002841beSOleksij Rempel 	int ret;
117002841beSOleksij Rempel 
118002841beSOleksij Rempel 	if (dissector->used_keys &
119002841beSOleksij Rempel 	    ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
120002841beSOleksij Rempel 	      BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
121002841beSOleksij Rempel 	      BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) {
122002841beSOleksij Rempel 		NL_SET_ERR_MSG_MOD(extack,
123002841beSOleksij Rempel 				   "Unsupported keys used");
124002841beSOleksij Rempel 		return -EOPNOTSUPP;
125002841beSOleksij Rempel 	}
126002841beSOleksij Rempel 
127*d9a1249eSAsbjørn Sloth Tønnesen 	if (flow_rule_match_has_control_flags(rule, extack))
128*d9a1249eSAsbjørn Sloth Tønnesen 		return -EOPNOTSUPP;
129*d9a1249eSAsbjørn Sloth Tønnesen 
130002841beSOleksij Rempel 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC) ||
131002841beSOleksij Rempel 	    flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
132002841beSOleksij Rempel 		ret = ksz9477_flower_parse_key_l2(dev, port, extack, rule,
133002841beSOleksij Rempel 						  cookie, prio);
134002841beSOleksij Rempel 		if (ret)
135002841beSOleksij Rempel 			return ret;
136002841beSOleksij Rempel 	}
137002841beSOleksij Rempel 
138002841beSOleksij Rempel 	return 0;
139002841beSOleksij Rempel }
140002841beSOleksij Rempel 
141002841beSOleksij Rempel /**
142002841beSOleksij Rempel  * ksz9477_flower_parse_action - Parse flow rule actions for a specified port
143002841beSOleksij Rempel  *				 on a ksz_device.
144002841beSOleksij Rempel  * @dev: The ksz_device instance.
145002841beSOleksij Rempel  * @port: The port number to parse the flow rule actions for.
146002841beSOleksij Rempel  * @extack: The netlink extended ACK for reporting errors.
147002841beSOleksij Rempel  * @cls: The flow_cls_offload instance containing the flow rule.
148002841beSOleksij Rempel  * @entry_idx: The index of the ACL entry to store the action.
149002841beSOleksij Rempel  *
150002841beSOleksij Rempel  * This function checks if the actions in the flow rule are supported by
151002841beSOleksij Rempel  * the device. Currently, only actions that change priorities are supported.
152002841beSOleksij Rempel  * If unsupported actions are encountered, an error message is set in the
153002841beSOleksij Rempel  * extended ACK.
154002841beSOleksij Rempel  *
155002841beSOleksij Rempel  * Returns: 0 on success or a negative error code on failure.
156002841beSOleksij Rempel  */
ksz9477_flower_parse_action(struct ksz_device * dev,int port,struct netlink_ext_ack * extack,struct flow_cls_offload * cls,int entry_idx)157002841beSOleksij Rempel static int ksz9477_flower_parse_action(struct ksz_device *dev, int port,
158002841beSOleksij Rempel 				       struct netlink_ext_ack *extack,
159002841beSOleksij Rempel 				       struct flow_cls_offload *cls,
160002841beSOleksij Rempel 				       int entry_idx)
161002841beSOleksij Rempel {
162002841beSOleksij Rempel 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
163002841beSOleksij Rempel 	struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv;
164002841beSOleksij Rempel 	const struct flow_action_entry *act;
165002841beSOleksij Rempel 	struct ksz9477_acl_entry *entry;
166002841beSOleksij Rempel 	bool prio_force = false;
167002841beSOleksij Rempel 	u8 prio_val = 0;
168002841beSOleksij Rempel 	int i;
169002841beSOleksij Rempel 
170002841beSOleksij Rempel 	if (TC_H_MIN(cls->classid)) {
171002841beSOleksij Rempel 		NL_SET_ERR_MSG_MOD(extack, "hw_tc is not supported. Use: action skbedit prio");
172002841beSOleksij Rempel 		return -EOPNOTSUPP;
173002841beSOleksij Rempel 	}
174002841beSOleksij Rempel 
175002841beSOleksij Rempel 	flow_action_for_each(i, act, &rule->action) {
176002841beSOleksij Rempel 		switch (act->id) {
177002841beSOleksij Rempel 		case FLOW_ACTION_PRIORITY:
178002841beSOleksij Rempel 			if (act->priority > KSZ9477_MAX_TC) {
179002841beSOleksij Rempel 				NL_SET_ERR_MSG_MOD(extack, "Priority value is too high");
180002841beSOleksij Rempel 				return -EOPNOTSUPP;
181002841beSOleksij Rempel 			}
182002841beSOleksij Rempel 			prio_force = true;
183002841beSOleksij Rempel 			prio_val = act->priority;
184002841beSOleksij Rempel 			break;
185002841beSOleksij Rempel 		default:
186002841beSOleksij Rempel 			NL_SET_ERR_MSG_MOD(extack, "action not supported");
187002841beSOleksij Rempel 			return -EOPNOTSUPP;
188002841beSOleksij Rempel 		}
189002841beSOleksij Rempel 	}
190002841beSOleksij Rempel 
191002841beSOleksij Rempel 	/* pick entry to store action */
192002841beSOleksij Rempel 	entry = &acl->acles.entries[entry_idx];
193002841beSOleksij Rempel 
194002841beSOleksij Rempel 	ksz9477_acl_action_rule_cfg(entry->entry, prio_force, prio_val);
195002841beSOleksij Rempel 	ksz9477_acl_processing_rule_set_action(entry->entry, entry_idx);
196002841beSOleksij Rempel 
197002841beSOleksij Rempel 	return 0;
198002841beSOleksij Rempel }
199002841beSOleksij Rempel 
200002841beSOleksij Rempel /**
201002841beSOleksij Rempel  * ksz9477_cls_flower_add - Add a flow classification rule for a specified port
202002841beSOleksij Rempel  *			    on a ksz_device.
203002841beSOleksij Rempel  * @ds: The DSA switch instance.
204002841beSOleksij Rempel  * @port: The port number to add the flow classification rule to.
205002841beSOleksij Rempel  * @cls: The flow_cls_offload instance containing the flow rule.
206002841beSOleksij Rempel  * @ingress: A flag indicating if the rule is applied on the ingress path.
207002841beSOleksij Rempel  *
208002841beSOleksij Rempel  * This function adds a flow classification rule for a specified port on a
209002841beSOleksij Rempel  * ksz_device. It checks if the ACL offloading is supported and parses the flow
210002841beSOleksij Rempel  * keys and actions. If the ACL is not supported, it returns an error. If there
211002841beSOleksij Rempel  * are unprocessed entries, it parses the action for the rule.
212002841beSOleksij Rempel  *
213002841beSOleksij Rempel  * Returns: 0 on success or a negative error code on failure.
214002841beSOleksij Rempel  */
ksz9477_cls_flower_add(struct dsa_switch * ds,int port,struct flow_cls_offload * cls,bool ingress)215002841beSOleksij Rempel int ksz9477_cls_flower_add(struct dsa_switch *ds, int port,
216002841beSOleksij Rempel 			   struct flow_cls_offload *cls, bool ingress)
217002841beSOleksij Rempel {
218002841beSOleksij Rempel 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
219002841beSOleksij Rempel 	struct netlink_ext_ack *extack = cls->common.extack;
220002841beSOleksij Rempel 	struct ksz_device *dev = ds->priv;
221002841beSOleksij Rempel 	struct ksz9477_acl_priv *acl;
222002841beSOleksij Rempel 	int action_entry_idx;
223002841beSOleksij Rempel 	int ret;
224002841beSOleksij Rempel 
225002841beSOleksij Rempel 	acl = dev->ports[port].acl_priv;
226002841beSOleksij Rempel 
227002841beSOleksij Rempel 	if (!acl) {
228002841beSOleksij Rempel 		NL_SET_ERR_MSG_MOD(extack, "ACL offloading is not supported");
229002841beSOleksij Rempel 		return -EOPNOTSUPP;
230002841beSOleksij Rempel 	}
231002841beSOleksij Rempel 
232002841beSOleksij Rempel 	/* A complex rule set can take multiple entries. Use first entry
233002841beSOleksij Rempel 	 * to store the action.
234002841beSOleksij Rempel 	 */
235002841beSOleksij Rempel 	action_entry_idx = acl->acles.entries_count;
236002841beSOleksij Rempel 
237002841beSOleksij Rempel 	ret = ksz9477_flower_parse_key(dev, port, extack, rule, cls->cookie,
238002841beSOleksij Rempel 				       cls->common.prio);
239002841beSOleksij Rempel 	if (ret)
240002841beSOleksij Rempel 		return ret;
241002841beSOleksij Rempel 
242002841beSOleksij Rempel 	ret = ksz9477_flower_parse_action(dev, port, extack, cls,
243002841beSOleksij Rempel 					  action_entry_idx);
244002841beSOleksij Rempel 	if (ret)
245002841beSOleksij Rempel 		return ret;
246002841beSOleksij Rempel 
247002841beSOleksij Rempel 	ret = ksz9477_sort_acl_entries(dev, port);
248002841beSOleksij Rempel 	if (ret)
249002841beSOleksij Rempel 		return ret;
250002841beSOleksij Rempel 
251002841beSOleksij Rempel 	return ksz9477_acl_write_list(dev, port);
252002841beSOleksij Rempel }
253002841beSOleksij Rempel 
254002841beSOleksij Rempel /**
255002841beSOleksij Rempel  * ksz9477_cls_flower_del - Remove a flow classification rule for a specified
256002841beSOleksij Rempel  *			    port on a ksz_device.
257002841beSOleksij Rempel  * @ds: The DSA switch instance.
258002841beSOleksij Rempel  * @port: The port number to remove the flow classification rule from.
259002841beSOleksij Rempel  * @cls: The flow_cls_offload instance containing the flow rule.
260002841beSOleksij Rempel  * @ingress: A flag indicating if the rule is applied on the ingress path.
261002841beSOleksij Rempel  *
262002841beSOleksij Rempel  * This function removes a flow classification rule for a specified port on a
263002841beSOleksij Rempel  * ksz_device. It checks if the ACL is initialized, and if not, returns an
264002841beSOleksij Rempel  * error. If the ACL is initialized, it removes entries with the specified
265002841beSOleksij Rempel  * cookie and rewrites the ACL list.
266002841beSOleksij Rempel  *
267002841beSOleksij Rempel  * Returns: 0 on success or a negative error code on failure.
268002841beSOleksij Rempel  */
ksz9477_cls_flower_del(struct dsa_switch * ds,int port,struct flow_cls_offload * cls,bool ingress)269002841beSOleksij Rempel int ksz9477_cls_flower_del(struct dsa_switch *ds, int port,
270002841beSOleksij Rempel 			   struct flow_cls_offload *cls, bool ingress)
271002841beSOleksij Rempel {
272002841beSOleksij Rempel 	unsigned long cookie = cls->cookie;
273002841beSOleksij Rempel 	struct ksz_device *dev = ds->priv;
274002841beSOleksij Rempel 	struct ksz9477_acl_priv *acl;
275002841beSOleksij Rempel 
276002841beSOleksij Rempel 	acl = dev->ports[port].acl_priv;
277002841beSOleksij Rempel 
278002841beSOleksij Rempel 	if (!acl)
279002841beSOleksij Rempel 		return -EOPNOTSUPP;
280002841beSOleksij Rempel 
281002841beSOleksij Rempel 	ksz9477_acl_remove_entries(dev, port, &acl->acles, cookie);
282002841beSOleksij Rempel 
283002841beSOleksij Rempel 	return ksz9477_acl_write_list(dev, port);
284002841beSOleksij Rempel }
285