1 /*
2  * drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c
3  * Copyright (c) 2017 Mellanox Technologies. All rights reserved.
4  * Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. Neither the names of the copyright holders nor the names of its
15  *    contributors may be used to endorse or promote products derived from
16  *    this software without specific prior written permission.
17  *
18  * Alternatively, this software may be distributed under the terms of the
19  * GNU General Public License ("GPL") version 2 as published by the Free
20  * Software Foundation.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #include <linux/kernel.h>
36 #include <linux/slab.h>
37 #include <linux/errno.h>
38 #include <linux/list.h>
39 #include <linux/string.h>
40 #include <linux/rhashtable.h>
41 #include <linux/netdevice.h>
42 
43 #include "reg.h"
44 #include "core.h"
45 #include "resources.h"
46 #include "spectrum.h"
47 #include "core_acl_flex_keys.h"
48 #include "core_acl_flex_actions.h"
49 #include "spectrum_acl_flex_keys.h"
50 
51 struct mlxsw_sp_acl {
52 	struct mlxsw_afk *afk;
53 	struct mlxsw_afa *afa;
54 	const struct mlxsw_sp_acl_ops *ops;
55 	struct rhashtable ruleset_ht;
56 	unsigned long priv[0];
57 	/* priv has to be always the last item */
58 };
59 
60 struct mlxsw_afk *mlxsw_sp_acl_afk(struct mlxsw_sp_acl *acl)
61 {
62 	return acl->afk;
63 }
64 
65 struct mlxsw_sp_acl_ruleset_ht_key {
66 	struct net_device *dev; /* dev this ruleset is bound to */
67 	bool ingress;
68 	const struct mlxsw_sp_acl_profile_ops *ops;
69 };
70 
71 struct mlxsw_sp_acl_ruleset {
72 	struct rhash_head ht_node; /* Member of acl HT */
73 	struct mlxsw_sp_acl_ruleset_ht_key ht_key;
74 	struct rhashtable rule_ht;
75 	unsigned int ref_count;
76 	unsigned long priv[0];
77 	/* priv has to be always the last item */
78 };
79 
80 struct mlxsw_sp_acl_rule {
81 	struct rhash_head ht_node; /* Member of rule HT */
82 	unsigned long cookie; /* HT key */
83 	struct mlxsw_sp_acl_ruleset *ruleset;
84 	struct mlxsw_sp_acl_rule_info *rulei;
85 	unsigned long priv[0];
86 	/* priv has to be always the last item */
87 };
88 
89 static const struct rhashtable_params mlxsw_sp_acl_ruleset_ht_params = {
90 	.key_len = sizeof(struct mlxsw_sp_acl_ruleset_ht_key),
91 	.key_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_key),
92 	.head_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_node),
93 	.automatic_shrinking = true,
94 };
95 
96 static const struct rhashtable_params mlxsw_sp_acl_rule_ht_params = {
97 	.key_len = sizeof(unsigned long),
98 	.key_offset = offsetof(struct mlxsw_sp_acl_rule, cookie),
99 	.head_offset = offsetof(struct mlxsw_sp_acl_rule, ht_node),
100 	.automatic_shrinking = true,
101 };
102 
103 static struct mlxsw_sp_acl_ruleset *
104 mlxsw_sp_acl_ruleset_create(struct mlxsw_sp *mlxsw_sp,
105 			    const struct mlxsw_sp_acl_profile_ops *ops)
106 {
107 	struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
108 	struct mlxsw_sp_acl_ruleset *ruleset;
109 	size_t alloc_size;
110 	int err;
111 
112 	alloc_size = sizeof(*ruleset) + ops->ruleset_priv_size;
113 	ruleset = kzalloc(alloc_size, GFP_KERNEL);
114 	if (!ruleset)
115 		return ERR_PTR(-ENOMEM);
116 	ruleset->ref_count = 1;
117 	ruleset->ht_key.ops = ops;
118 
119 	err = rhashtable_init(&ruleset->rule_ht, &mlxsw_sp_acl_rule_ht_params);
120 	if (err)
121 		goto err_rhashtable_init;
122 
123 	err = ops->ruleset_add(mlxsw_sp, acl->priv, ruleset->priv);
124 	if (err)
125 		goto err_ops_ruleset_add;
126 
127 	return ruleset;
128 
129 err_ops_ruleset_add:
130 	rhashtable_destroy(&ruleset->rule_ht);
131 err_rhashtable_init:
132 	kfree(ruleset);
133 	return ERR_PTR(err);
134 }
135 
136 static void mlxsw_sp_acl_ruleset_destroy(struct mlxsw_sp *mlxsw_sp,
137 					 struct mlxsw_sp_acl_ruleset *ruleset)
138 {
139 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
140 
141 	ops->ruleset_del(mlxsw_sp, ruleset->priv);
142 	rhashtable_destroy(&ruleset->rule_ht);
143 	kfree(ruleset);
144 }
145 
146 static int mlxsw_sp_acl_ruleset_bind(struct mlxsw_sp *mlxsw_sp,
147 				     struct mlxsw_sp_acl_ruleset *ruleset,
148 				     struct net_device *dev, bool ingress)
149 {
150 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
151 	struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
152 	int err;
153 
154 	ruleset->ht_key.dev = dev;
155 	ruleset->ht_key.ingress = ingress;
156 	err = rhashtable_insert_fast(&acl->ruleset_ht, &ruleset->ht_node,
157 				     mlxsw_sp_acl_ruleset_ht_params);
158 	if (err)
159 		return err;
160 	err = ops->ruleset_bind(mlxsw_sp, ruleset->priv, dev, ingress);
161 	if (err)
162 		goto err_ops_ruleset_bind;
163 	return 0;
164 
165 err_ops_ruleset_bind:
166 	rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
167 			       mlxsw_sp_acl_ruleset_ht_params);
168 	return err;
169 }
170 
171 static void mlxsw_sp_acl_ruleset_unbind(struct mlxsw_sp *mlxsw_sp,
172 					struct mlxsw_sp_acl_ruleset *ruleset)
173 {
174 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
175 	struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
176 
177 	ops->ruleset_unbind(mlxsw_sp, ruleset->priv);
178 	rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
179 			       mlxsw_sp_acl_ruleset_ht_params);
180 }
181 
182 static void mlxsw_sp_acl_ruleset_ref_inc(struct mlxsw_sp_acl_ruleset *ruleset)
183 {
184 	ruleset->ref_count++;
185 }
186 
187 static void mlxsw_sp_acl_ruleset_ref_dec(struct mlxsw_sp *mlxsw_sp,
188 					 struct mlxsw_sp_acl_ruleset *ruleset)
189 {
190 	if (--ruleset->ref_count)
191 		return;
192 	mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, ruleset);
193 	mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
194 }
195 
196 struct mlxsw_sp_acl_ruleset *
197 mlxsw_sp_acl_ruleset_get(struct mlxsw_sp *mlxsw_sp,
198 			 struct net_device *dev, bool ingress,
199 			 enum mlxsw_sp_acl_profile profile)
200 {
201 	const struct mlxsw_sp_acl_profile_ops *ops;
202 	struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
203 	struct mlxsw_sp_acl_ruleset_ht_key ht_key;
204 	struct mlxsw_sp_acl_ruleset *ruleset;
205 	int err;
206 
207 	ops = acl->ops->profile_ops(mlxsw_sp, profile);
208 	if (!ops)
209 		return ERR_PTR(-EINVAL);
210 
211 	memset(&ht_key, 0, sizeof(ht_key));
212 	ht_key.dev = dev;
213 	ht_key.ingress = ingress;
214 	ht_key.ops = ops;
215 	ruleset = rhashtable_lookup_fast(&acl->ruleset_ht, &ht_key,
216 					 mlxsw_sp_acl_ruleset_ht_params);
217 	if (ruleset) {
218 		mlxsw_sp_acl_ruleset_ref_inc(ruleset);
219 		return ruleset;
220 	}
221 	ruleset = mlxsw_sp_acl_ruleset_create(mlxsw_sp, ops);
222 	if (IS_ERR(ruleset))
223 		return ruleset;
224 	err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, ruleset, dev, ingress);
225 	if (err)
226 		goto err_ruleset_bind;
227 	return ruleset;
228 
229 err_ruleset_bind:
230 	mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
231 	return ERR_PTR(err);
232 }
233 
234 void mlxsw_sp_acl_ruleset_put(struct mlxsw_sp *mlxsw_sp,
235 			      struct mlxsw_sp_acl_ruleset *ruleset)
236 {
237 	mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
238 }
239 
240 struct mlxsw_sp_acl_rule_info *
241 mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl)
242 {
243 	struct mlxsw_sp_acl_rule_info *rulei;
244 	int err;
245 
246 	rulei = kzalloc(sizeof(*rulei), GFP_KERNEL);
247 	if (!rulei)
248 		return NULL;
249 	rulei->act_block = mlxsw_afa_block_create(acl->afa);
250 	if (IS_ERR(rulei->act_block)) {
251 		err = PTR_ERR(rulei->act_block);
252 		goto err_afa_block_create;
253 	}
254 	return rulei;
255 
256 err_afa_block_create:
257 	kfree(rulei);
258 	return ERR_PTR(err);
259 }
260 
261 void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp_acl_rule_info *rulei)
262 {
263 	mlxsw_afa_block_destroy(rulei->act_block);
264 	kfree(rulei);
265 }
266 
267 int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei)
268 {
269 	return mlxsw_afa_block_commit(rulei->act_block);
270 }
271 
272 void mlxsw_sp_acl_rulei_priority(struct mlxsw_sp_acl_rule_info *rulei,
273 				 unsigned int priority)
274 {
275 	rulei->priority = priority;
276 }
277 
278 void mlxsw_sp_acl_rulei_keymask_u32(struct mlxsw_sp_acl_rule_info *rulei,
279 				    enum mlxsw_afk_element element,
280 				    u32 key_value, u32 mask_value)
281 {
282 	mlxsw_afk_values_add_u32(&rulei->values, element,
283 				 key_value, mask_value);
284 }
285 
286 void mlxsw_sp_acl_rulei_keymask_buf(struct mlxsw_sp_acl_rule_info *rulei,
287 				    enum mlxsw_afk_element element,
288 				    const char *key_value,
289 				    const char *mask_value, unsigned int len)
290 {
291 	mlxsw_afk_values_add_buf(&rulei->values, element,
292 				 key_value, mask_value, len);
293 }
294 
295 void mlxsw_sp_acl_rulei_act_continue(struct mlxsw_sp_acl_rule_info *rulei)
296 {
297 	mlxsw_afa_block_continue(rulei->act_block);
298 }
299 
300 void mlxsw_sp_acl_rulei_act_jump(struct mlxsw_sp_acl_rule_info *rulei,
301 				 u16 group_id)
302 {
303 	mlxsw_afa_block_jump(rulei->act_block, group_id);
304 }
305 
306 int mlxsw_sp_acl_rulei_act_drop(struct mlxsw_sp_acl_rule_info *rulei)
307 {
308 	return mlxsw_afa_block_append_drop(rulei->act_block);
309 }
310 
311 int mlxsw_sp_acl_rulei_act_fwd(struct mlxsw_sp *mlxsw_sp,
312 			       struct mlxsw_sp_acl_rule_info *rulei,
313 			       struct net_device *out_dev)
314 {
315 	struct mlxsw_sp_port *mlxsw_sp_port;
316 	u8 local_port;
317 	bool in_port;
318 
319 	if (out_dev) {
320 		if (!mlxsw_sp_port_dev_check(out_dev))
321 			return -EINVAL;
322 		mlxsw_sp_port = netdev_priv(out_dev);
323 		if (mlxsw_sp_port->mlxsw_sp != mlxsw_sp)
324 			return -EINVAL;
325 		local_port = mlxsw_sp_port->local_port;
326 		in_port = false;
327 	} else {
328 		/* If out_dev is NULL, the called wants to
329 		 * set forward to ingress port.
330 		 */
331 		local_port = 0;
332 		in_port = true;
333 	}
334 	return mlxsw_afa_block_append_fwd(rulei->act_block,
335 					  local_port, in_port);
336 }
337 
338 struct mlxsw_sp_acl_rule *
339 mlxsw_sp_acl_rule_create(struct mlxsw_sp *mlxsw_sp,
340 			 struct mlxsw_sp_acl_ruleset *ruleset,
341 			 unsigned long cookie)
342 {
343 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
344 	struct mlxsw_sp_acl_rule *rule;
345 	int err;
346 
347 	mlxsw_sp_acl_ruleset_ref_inc(ruleset);
348 	rule = kzalloc(sizeof(*rule) + ops->rule_priv_size, GFP_KERNEL);
349 	if (!rule) {
350 		err = -ENOMEM;
351 		goto err_alloc;
352 	}
353 	rule->cookie = cookie;
354 	rule->ruleset = ruleset;
355 
356 	rule->rulei = mlxsw_sp_acl_rulei_create(mlxsw_sp->acl);
357 	if (IS_ERR(rule->rulei)) {
358 		err = PTR_ERR(rule->rulei);
359 		goto err_rulei_create;
360 	}
361 	return rule;
362 
363 err_rulei_create:
364 	kfree(rule);
365 err_alloc:
366 	mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
367 	return ERR_PTR(err);
368 }
369 
370 void mlxsw_sp_acl_rule_destroy(struct mlxsw_sp *mlxsw_sp,
371 			       struct mlxsw_sp_acl_rule *rule)
372 {
373 	struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
374 
375 	mlxsw_sp_acl_rulei_destroy(rule->rulei);
376 	kfree(rule);
377 	mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
378 }
379 
380 int mlxsw_sp_acl_rule_add(struct mlxsw_sp *mlxsw_sp,
381 			  struct mlxsw_sp_acl_rule *rule)
382 {
383 	struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
384 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
385 	int err;
386 
387 	err = ops->rule_add(mlxsw_sp, ruleset->priv, rule->priv, rule->rulei);
388 	if (err)
389 		return err;
390 
391 	err = rhashtable_insert_fast(&ruleset->rule_ht, &rule->ht_node,
392 				     mlxsw_sp_acl_rule_ht_params);
393 	if (err)
394 		goto err_rhashtable_insert;
395 
396 	return 0;
397 
398 err_rhashtable_insert:
399 	ops->rule_del(mlxsw_sp, rule->priv);
400 	return err;
401 }
402 
403 void mlxsw_sp_acl_rule_del(struct mlxsw_sp *mlxsw_sp,
404 			   struct mlxsw_sp_acl_rule *rule)
405 {
406 	struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
407 	const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
408 
409 	rhashtable_remove_fast(&ruleset->rule_ht, &rule->ht_node,
410 			       mlxsw_sp_acl_rule_ht_params);
411 	ops->rule_del(mlxsw_sp, rule->priv);
412 }
413 
414 struct mlxsw_sp_acl_rule *
415 mlxsw_sp_acl_rule_lookup(struct mlxsw_sp *mlxsw_sp,
416 			 struct mlxsw_sp_acl_ruleset *ruleset,
417 			 unsigned long cookie)
418 {
419 	return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie,
420 				       mlxsw_sp_acl_rule_ht_params);
421 }
422 
423 struct mlxsw_sp_acl_rule_info *
424 mlxsw_sp_acl_rule_rulei(struct mlxsw_sp_acl_rule *rule)
425 {
426 	return rule->rulei;
427 }
428 
429 #define MLXSW_SP_KDVL_ACT_EXT_SIZE 1
430 
431 static int mlxsw_sp_act_kvdl_set_add(void *priv, u32 *p_kvdl_index,
432 				     char *enc_actions, bool is_first)
433 {
434 	struct mlxsw_sp *mlxsw_sp = priv;
435 	char pefa_pl[MLXSW_REG_PEFA_LEN];
436 	u32 kvdl_index;
437 	int ret;
438 	int err;
439 
440 	/* The first action set of a TCAM entry is stored directly in TCAM,
441 	 * not KVD linear area.
442 	 */
443 	if (is_first)
444 		return 0;
445 
446 	ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KDVL_ACT_EXT_SIZE);
447 	if (ret < 0)
448 		return ret;
449 	kvdl_index = ret;
450 	mlxsw_reg_pefa_pack(pefa_pl, kvdl_index, enc_actions);
451 	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pefa), pefa_pl);
452 	if (err)
453 		goto err_pefa_write;
454 	*p_kvdl_index = kvdl_index;
455 	return 0;
456 
457 err_pefa_write:
458 	mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
459 	return err;
460 }
461 
462 static void mlxsw_sp_act_kvdl_set_del(void *priv, u32 kvdl_index,
463 				      bool is_first)
464 {
465 	struct mlxsw_sp *mlxsw_sp = priv;
466 
467 	if (is_first)
468 		return;
469 	mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
470 }
471 
472 static int mlxsw_sp_act_kvdl_fwd_entry_add(void *priv, u32 *p_kvdl_index,
473 					   u8 local_port)
474 {
475 	struct mlxsw_sp *mlxsw_sp = priv;
476 	char ppbs_pl[MLXSW_REG_PPBS_LEN];
477 	u32 kvdl_index;
478 	int ret;
479 	int err;
480 
481 	ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, 1);
482 	if (ret < 0)
483 		return ret;
484 	kvdl_index = ret;
485 	mlxsw_reg_ppbs_pack(ppbs_pl, kvdl_index, local_port);
486 	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbs), ppbs_pl);
487 	if (err)
488 		goto err_ppbs_write;
489 	*p_kvdl_index = kvdl_index;
490 	return 0;
491 
492 err_ppbs_write:
493 	mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
494 	return err;
495 }
496 
497 static void mlxsw_sp_act_kvdl_fwd_entry_del(void *priv, u32 kvdl_index)
498 {
499 	struct mlxsw_sp *mlxsw_sp = priv;
500 
501 	mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
502 }
503 
504 static const struct mlxsw_afa_ops mlxsw_sp_act_afa_ops = {
505 	.kvdl_set_add		= mlxsw_sp_act_kvdl_set_add,
506 	.kvdl_set_del		= mlxsw_sp_act_kvdl_set_del,
507 	.kvdl_fwd_entry_add	= mlxsw_sp_act_kvdl_fwd_entry_add,
508 	.kvdl_fwd_entry_del	= mlxsw_sp_act_kvdl_fwd_entry_del,
509 };
510 
511 int mlxsw_sp_acl_init(struct mlxsw_sp *mlxsw_sp)
512 {
513 	const struct mlxsw_sp_acl_ops *acl_ops = &mlxsw_sp_acl_tcam_ops;
514 	struct mlxsw_sp_acl *acl;
515 	int err;
516 
517 	acl = kzalloc(sizeof(*acl) + acl_ops->priv_size, GFP_KERNEL);
518 	if (!acl)
519 		return -ENOMEM;
520 	mlxsw_sp->acl = acl;
521 
522 	acl->afk = mlxsw_afk_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
523 						       ACL_FLEX_KEYS),
524 				    mlxsw_sp_afk_blocks,
525 				    MLXSW_SP_AFK_BLOCKS_COUNT);
526 	if (!acl->afk) {
527 		err = -ENOMEM;
528 		goto err_afk_create;
529 	}
530 
531 	acl->afa = mlxsw_afa_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
532 						       ACL_ACTIONS_PER_SET),
533 				    &mlxsw_sp_act_afa_ops, mlxsw_sp);
534 	if (IS_ERR(acl->afa)) {
535 		err = PTR_ERR(acl->afa);
536 		goto err_afa_create;
537 	}
538 
539 	err = rhashtable_init(&acl->ruleset_ht,
540 			      &mlxsw_sp_acl_ruleset_ht_params);
541 	if (err)
542 		goto err_rhashtable_init;
543 
544 	err = acl_ops->init(mlxsw_sp, acl->priv);
545 	if (err)
546 		goto err_acl_ops_init;
547 
548 	acl->ops = acl_ops;
549 	return 0;
550 
551 err_acl_ops_init:
552 	rhashtable_destroy(&acl->ruleset_ht);
553 err_rhashtable_init:
554 	mlxsw_afa_destroy(acl->afa);
555 err_afa_create:
556 	mlxsw_afk_destroy(acl->afk);
557 err_afk_create:
558 	kfree(acl);
559 	return err;
560 }
561 
562 void mlxsw_sp_acl_fini(struct mlxsw_sp *mlxsw_sp)
563 {
564 	struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
565 	const struct mlxsw_sp_acl_ops *acl_ops = acl->ops;
566 
567 	acl_ops->fini(mlxsw_sp, acl->priv);
568 	rhashtable_destroy(&acl->ruleset_ht);
569 	mlxsw_afa_destroy(acl->afa);
570 	mlxsw_afk_destroy(acl->afk);
571 	kfree(acl);
572 }
573