1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* Copyright (c) 2020-2021 Marvell International Ltd. All rights reserved */ |
3 | |
4 | #include <linux/rhashtable.h> |
5 | |
6 | #include "prestera_acl.h" |
7 | #include "prestera_flow.h" |
8 | #include "prestera_hw.h" |
9 | #include "prestera.h" |
10 | |
11 | #define ACL_KEYMASK_SIZE \ |
12 | (sizeof(__be32) * __PRESTERA_ACL_RULE_MATCH_TYPE_MAX) |
13 | |
14 | struct prestera_acl { |
15 | struct prestera_switch *sw; |
16 | struct list_head vtcam_list; |
17 | struct list_head rules; |
18 | struct rhashtable ruleset_ht; |
19 | struct rhashtable acl_rule_entry_ht; |
20 | struct idr uid; |
21 | }; |
22 | |
23 | struct prestera_acl_ruleset_ht_key { |
24 | struct prestera_flow_block *block; |
25 | u32 chain_index; |
26 | }; |
27 | |
28 | struct prestera_acl_rule_entry { |
29 | struct rhash_head ht_node; |
30 | struct prestera_acl_rule_entry_key key; |
31 | u32 hw_id; |
32 | u32 vtcam_id; |
33 | struct { |
34 | struct { |
35 | u8 valid:1; |
36 | } accept, drop, trap; |
37 | struct { |
38 | u8 valid:1; |
39 | struct prestera_acl_action_police i; |
40 | } police; |
41 | struct { |
42 | struct prestera_acl_action_jump i; |
43 | u8 valid:1; |
44 | } jump; |
45 | struct { |
46 | u32 id; |
47 | struct prestera_counter_block *block; |
48 | } counter; |
49 | }; |
50 | }; |
51 | |
52 | struct prestera_acl_ruleset { |
53 | struct rhash_head ht_node; /* Member of acl HT */ |
54 | struct prestera_acl_ruleset_ht_key ht_key; |
55 | struct rhashtable rule_ht; |
56 | struct prestera_acl *acl; |
57 | struct { |
58 | u32 min; |
59 | u32 max; |
60 | } prio; |
61 | unsigned long rule_count; |
62 | refcount_t refcount; |
63 | void *keymask; |
64 | u32 vtcam_id; |
65 | u32 index; |
66 | u16 pcl_id; |
67 | bool offload; |
68 | bool ingress; |
69 | }; |
70 | |
71 | struct prestera_acl_vtcam { |
72 | struct list_head list; |
73 | __be32 keymask[__PRESTERA_ACL_RULE_MATCH_TYPE_MAX]; |
74 | refcount_t refcount; |
75 | u32 id; |
76 | bool is_keymask_set; |
77 | u8 lookup; |
78 | u8 direction; |
79 | }; |
80 | |
81 | static const struct rhashtable_params prestera_acl_ruleset_ht_params = { |
82 | .key_len = sizeof(struct prestera_acl_ruleset_ht_key), |
83 | .key_offset = offsetof(struct prestera_acl_ruleset, ht_key), |
84 | .head_offset = offsetof(struct prestera_acl_ruleset, ht_node), |
85 | .automatic_shrinking = true, |
86 | }; |
87 | |
88 | static const struct rhashtable_params prestera_acl_rule_ht_params = { |
89 | .key_len = sizeof(unsigned long), |
90 | .key_offset = offsetof(struct prestera_acl_rule, cookie), |
91 | .head_offset = offsetof(struct prestera_acl_rule, ht_node), |
92 | .automatic_shrinking = true, |
93 | }; |
94 | |
95 | static const struct rhashtable_params __prestera_acl_rule_entry_ht_params = { |
96 | .key_offset = offsetof(struct prestera_acl_rule_entry, key), |
97 | .head_offset = offsetof(struct prestera_acl_rule_entry, ht_node), |
98 | .key_len = sizeof(struct prestera_acl_rule_entry_key), |
99 | .automatic_shrinking = true, |
100 | }; |
101 | |
102 | int prestera_acl_chain_to_client(u32 chain_index, bool ingress, u32 *client) |
103 | { |
104 | static const u32 ingress_client_map[] = { |
105 | PRESTERA_HW_COUNTER_CLIENT_INGRESS_LOOKUP_0, |
106 | PRESTERA_HW_COUNTER_CLIENT_INGRESS_LOOKUP_1, |
107 | PRESTERA_HW_COUNTER_CLIENT_INGRESS_LOOKUP_2 |
108 | }; |
109 | |
110 | if (!ingress) { |
111 | /* prestera supports only one chain on egress */ |
112 | if (chain_index > 0) |
113 | return -EINVAL; |
114 | |
115 | *client = PRESTERA_HW_COUNTER_CLIENT_EGRESS_LOOKUP; |
116 | return 0; |
117 | } |
118 | |
119 | if (chain_index >= ARRAY_SIZE(ingress_client_map)) |
120 | return -EINVAL; |
121 | |
122 | *client = ingress_client_map[chain_index]; |
123 | return 0; |
124 | } |
125 | |
126 | static bool prestera_acl_chain_is_supported(u32 chain_index, bool ingress) |
127 | { |
128 | if (!ingress) |
129 | /* prestera supports only one chain on egress */ |
130 | return chain_index == 0; |
131 | |
132 | return (chain_index & ~PRESTERA_ACL_CHAIN_MASK) == 0; |
133 | } |
134 | |
135 | static struct prestera_acl_ruleset * |
136 | prestera_acl_ruleset_create(struct prestera_acl *acl, |
137 | struct prestera_flow_block *block, |
138 | u32 chain_index) |
139 | { |
140 | struct prestera_acl_ruleset *ruleset; |
141 | u32 uid = 0; |
142 | int err; |
143 | |
144 | if (!prestera_acl_chain_is_supported(chain_index, ingress: block->ingress)) |
145 | return ERR_PTR(error: -EINVAL); |
146 | |
147 | ruleset = kzalloc(size: sizeof(*ruleset), GFP_KERNEL); |
148 | if (!ruleset) |
149 | return ERR_PTR(error: -ENOMEM); |
150 | |
151 | ruleset->acl = acl; |
152 | ruleset->ingress = block->ingress; |
153 | ruleset->ht_key.block = block; |
154 | ruleset->ht_key.chain_index = chain_index; |
155 | refcount_set(r: &ruleset->refcount, n: 1); |
156 | |
157 | err = rhashtable_init(ht: &ruleset->rule_ht, params: &prestera_acl_rule_ht_params); |
158 | if (err) |
159 | goto err_rhashtable_init; |
160 | |
161 | err = idr_alloc_u32(&acl->uid, NULL, id: &uid, U8_MAX, GFP_KERNEL); |
162 | if (err) |
163 | goto err_ruleset_create; |
164 | |
165 | /* make pcl-id based on uid */ |
166 | ruleset->pcl_id = PRESTERA_ACL_PCL_ID_MAKE((u8)uid, chain_index); |
167 | ruleset->index = uid; |
168 | |
169 | ruleset->prio.min = UINT_MAX; |
170 | ruleset->prio.max = 0; |
171 | |
172 | err = rhashtable_insert_fast(ht: &acl->ruleset_ht, obj: &ruleset->ht_node, |
173 | params: prestera_acl_ruleset_ht_params); |
174 | if (err) |
175 | goto err_ruleset_ht_insert; |
176 | |
177 | return ruleset; |
178 | |
179 | err_ruleset_ht_insert: |
180 | idr_remove(&acl->uid, id: uid); |
181 | err_ruleset_create: |
182 | rhashtable_destroy(ht: &ruleset->rule_ht); |
183 | err_rhashtable_init: |
184 | kfree(objp: ruleset); |
185 | return ERR_PTR(error: err); |
186 | } |
187 | |
188 | int prestera_acl_ruleset_keymask_set(struct prestera_acl_ruleset *ruleset, |
189 | void *keymask) |
190 | { |
191 | ruleset->keymask = kmemdup(p: keymask, ACL_KEYMASK_SIZE, GFP_KERNEL); |
192 | if (!ruleset->keymask) |
193 | return -ENOMEM; |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | int prestera_acl_ruleset_offload(struct prestera_acl_ruleset *ruleset) |
199 | { |
200 | struct prestera_acl_iface iface; |
201 | u32 vtcam_id; |
202 | int dir; |
203 | int err; |
204 | |
205 | dir = ruleset->ingress ? |
206 | PRESTERA_HW_VTCAM_DIR_INGRESS : PRESTERA_HW_VTCAM_DIR_EGRESS; |
207 | |
208 | if (ruleset->offload) |
209 | return -EEXIST; |
210 | |
211 | err = prestera_acl_vtcam_id_get(acl: ruleset->acl, |
212 | lookup: ruleset->ht_key.chain_index, |
213 | dir, |
214 | keymask: ruleset->keymask, vtcam_id: &vtcam_id); |
215 | if (err) |
216 | goto err_vtcam_create; |
217 | |
218 | if (ruleset->ht_key.chain_index) { |
219 | /* for chain > 0, bind iface index to pcl-id to be able |
220 | * to jump from any other ruleset to this one using the index. |
221 | */ |
222 | iface.index = ruleset->index; |
223 | iface.type = PRESTERA_ACL_IFACE_TYPE_INDEX; |
224 | err = prestera_hw_vtcam_iface_bind(sw: ruleset->acl->sw, iface: &iface, |
225 | vtcam_id, pcl_id: ruleset->pcl_id); |
226 | if (err) |
227 | goto err_ruleset_bind; |
228 | } |
229 | |
230 | ruleset->vtcam_id = vtcam_id; |
231 | ruleset->offload = true; |
232 | return 0; |
233 | |
234 | err_ruleset_bind: |
235 | prestera_acl_vtcam_id_put(acl: ruleset->acl, vtcam_id: ruleset->vtcam_id); |
236 | err_vtcam_create: |
237 | return err; |
238 | } |
239 | |
240 | static void prestera_acl_ruleset_destroy(struct prestera_acl_ruleset *ruleset) |
241 | { |
242 | struct prestera_acl *acl = ruleset->acl; |
243 | u8 uid = ruleset->pcl_id & PRESTERA_ACL_KEYMASK_PCL_ID_USER; |
244 | int err; |
245 | |
246 | rhashtable_remove_fast(ht: &acl->ruleset_ht, obj: &ruleset->ht_node, |
247 | params: prestera_acl_ruleset_ht_params); |
248 | |
249 | if (ruleset->offload) { |
250 | if (ruleset->ht_key.chain_index) { |
251 | struct prestera_acl_iface iface = { |
252 | .type = PRESTERA_ACL_IFACE_TYPE_INDEX, |
253 | .index = ruleset->index |
254 | }; |
255 | err = prestera_hw_vtcam_iface_unbind(sw: acl->sw, iface: &iface, |
256 | vtcam_id: ruleset->vtcam_id); |
257 | WARN_ON(err); |
258 | } |
259 | WARN_ON(prestera_acl_vtcam_id_put(acl, ruleset->vtcam_id)); |
260 | } |
261 | |
262 | idr_remove(&acl->uid, id: uid); |
263 | rhashtable_destroy(ht: &ruleset->rule_ht); |
264 | kfree(objp: ruleset->keymask); |
265 | kfree(objp: ruleset); |
266 | } |
267 | |
268 | static struct prestera_acl_ruleset * |
269 | __prestera_acl_ruleset_lookup(struct prestera_acl *acl, |
270 | struct prestera_flow_block *block, |
271 | u32 chain_index) |
272 | { |
273 | struct prestera_acl_ruleset_ht_key ht_key; |
274 | |
275 | memset(&ht_key, 0, sizeof(ht_key)); |
276 | ht_key.block = block; |
277 | ht_key.chain_index = chain_index; |
278 | return rhashtable_lookup_fast(ht: &acl->ruleset_ht, key: &ht_key, |
279 | params: prestera_acl_ruleset_ht_params); |
280 | } |
281 | |
282 | struct prestera_acl_ruleset * |
283 | prestera_acl_ruleset_lookup(struct prestera_acl *acl, |
284 | struct prestera_flow_block *block, |
285 | u32 chain_index) |
286 | { |
287 | struct prestera_acl_ruleset *ruleset; |
288 | |
289 | ruleset = __prestera_acl_ruleset_lookup(acl, block, chain_index); |
290 | if (!ruleset) |
291 | return ERR_PTR(error: -ENOENT); |
292 | |
293 | refcount_inc(r: &ruleset->refcount); |
294 | return ruleset; |
295 | } |
296 | |
297 | struct prestera_acl_ruleset * |
298 | prestera_acl_ruleset_get(struct prestera_acl *acl, |
299 | struct prestera_flow_block *block, |
300 | u32 chain_index) |
301 | { |
302 | struct prestera_acl_ruleset *ruleset; |
303 | |
304 | ruleset = __prestera_acl_ruleset_lookup(acl, block, chain_index); |
305 | if (ruleset) { |
306 | refcount_inc(r: &ruleset->refcount); |
307 | return ruleset; |
308 | } |
309 | |
310 | return prestera_acl_ruleset_create(acl, block, chain_index); |
311 | } |
312 | |
313 | void prestera_acl_ruleset_put(struct prestera_acl_ruleset *ruleset) |
314 | { |
315 | if (!refcount_dec_and_test(r: &ruleset->refcount)) |
316 | return; |
317 | |
318 | prestera_acl_ruleset_destroy(ruleset); |
319 | } |
320 | |
321 | int prestera_acl_ruleset_bind(struct prestera_acl_ruleset *ruleset, |
322 | struct prestera_port *port) |
323 | { |
324 | struct prestera_acl_iface iface = { |
325 | .type = PRESTERA_ACL_IFACE_TYPE_PORT, |
326 | .port = port |
327 | }; |
328 | |
329 | return prestera_hw_vtcam_iface_bind(sw: port->sw, iface: &iface, vtcam_id: ruleset->vtcam_id, |
330 | pcl_id: ruleset->pcl_id); |
331 | } |
332 | |
333 | int prestera_acl_ruleset_unbind(struct prestera_acl_ruleset *ruleset, |
334 | struct prestera_port *port) |
335 | { |
336 | struct prestera_acl_iface iface = { |
337 | .type = PRESTERA_ACL_IFACE_TYPE_PORT, |
338 | .port = port |
339 | }; |
340 | |
341 | return prestera_hw_vtcam_iface_unbind(sw: port->sw, iface: &iface, |
342 | vtcam_id: ruleset->vtcam_id); |
343 | } |
344 | |
345 | static int prestera_acl_ruleset_block_bind(struct prestera_acl_ruleset *ruleset, |
346 | struct prestera_flow_block *block) |
347 | { |
348 | struct prestera_flow_block_binding *binding; |
349 | int err; |
350 | |
351 | block->ruleset_zero = ruleset; |
352 | list_for_each_entry(binding, &block->binding_list, list) { |
353 | err = prestera_acl_ruleset_bind(ruleset, port: binding->port); |
354 | if (err) |
355 | goto rollback; |
356 | } |
357 | return 0; |
358 | |
359 | rollback: |
360 | list_for_each_entry_continue_reverse(binding, &block->binding_list, |
361 | list) |
362 | err = prestera_acl_ruleset_unbind(ruleset, port: binding->port); |
363 | block->ruleset_zero = NULL; |
364 | |
365 | return err; |
366 | } |
367 | |
368 | static void |
369 | prestera_acl_ruleset_block_unbind(struct prestera_acl_ruleset *ruleset, |
370 | struct prestera_flow_block *block) |
371 | { |
372 | struct prestera_flow_block_binding *binding; |
373 | |
374 | list_for_each_entry(binding, &block->binding_list, list) |
375 | prestera_acl_ruleset_unbind(ruleset, port: binding->port); |
376 | block->ruleset_zero = NULL; |
377 | } |
378 | |
379 | static void |
380 | prestera_acl_ruleset_prio_refresh(struct prestera_acl *acl, |
381 | struct prestera_acl_ruleset *ruleset) |
382 | { |
383 | struct prestera_acl_rule *rule; |
384 | |
385 | ruleset->prio.min = UINT_MAX; |
386 | ruleset->prio.max = 0; |
387 | |
388 | list_for_each_entry(rule, &acl->rules, list) { |
389 | if (ruleset->ingress != rule->ruleset->ingress) |
390 | continue; |
391 | if (ruleset->ht_key.chain_index != rule->chain_index) |
392 | continue; |
393 | |
394 | ruleset->prio.min = min(ruleset->prio.min, rule->priority); |
395 | ruleset->prio.max = max(ruleset->prio.max, rule->priority); |
396 | } |
397 | } |
398 | |
399 | void |
400 | prestera_acl_rule_keymask_pcl_id_set(struct prestera_acl_rule *rule, u16 pcl_id) |
401 | { |
402 | struct prestera_acl_match *r_match = &rule->re_key.match; |
403 | __be16 pcl_id_mask = htons(PRESTERA_ACL_KEYMASK_PCL_ID); |
404 | __be16 pcl_id_key = htons(pcl_id); |
405 | |
406 | rule_match_set(r_match->key, PCL_ID, pcl_id_key); |
407 | rule_match_set(r_match->mask, PCL_ID, pcl_id_mask); |
408 | } |
409 | |
410 | struct prestera_acl_rule * |
411 | prestera_acl_rule_lookup(struct prestera_acl_ruleset *ruleset, |
412 | unsigned long cookie) |
413 | { |
414 | return rhashtable_lookup_fast(ht: &ruleset->rule_ht, key: &cookie, |
415 | params: prestera_acl_rule_ht_params); |
416 | } |
417 | |
418 | u32 prestera_acl_ruleset_index_get(const struct prestera_acl_ruleset *ruleset) |
419 | { |
420 | return ruleset->index; |
421 | } |
422 | |
423 | void prestera_acl_ruleset_prio_get(struct prestera_acl_ruleset *ruleset, |
424 | u32 *prio_min, u32 *prio_max) |
425 | { |
426 | *prio_min = ruleset->prio.min; |
427 | *prio_max = ruleset->prio.max; |
428 | } |
429 | |
430 | bool prestera_acl_ruleset_is_offload(struct prestera_acl_ruleset *ruleset) |
431 | { |
432 | return ruleset->offload; |
433 | } |
434 | |
435 | struct prestera_acl_rule * |
436 | prestera_acl_rule_create(struct prestera_acl_ruleset *ruleset, |
437 | unsigned long cookie, u32 chain_index) |
438 | { |
439 | struct prestera_acl_rule *rule; |
440 | |
441 | rule = kzalloc(size: sizeof(*rule), GFP_KERNEL); |
442 | if (!rule) |
443 | return ERR_PTR(error: -ENOMEM); |
444 | |
445 | rule->ruleset = ruleset; |
446 | rule->cookie = cookie; |
447 | rule->chain_index = chain_index; |
448 | |
449 | refcount_inc(r: &ruleset->refcount); |
450 | |
451 | return rule; |
452 | } |
453 | |
454 | void prestera_acl_rule_priority_set(struct prestera_acl_rule *rule, |
455 | u32 priority) |
456 | { |
457 | rule->priority = priority; |
458 | } |
459 | |
460 | void prestera_acl_rule_destroy(struct prestera_acl_rule *rule) |
461 | { |
462 | if (rule->jump_ruleset) |
463 | /* release ruleset kept by jump action */ |
464 | prestera_acl_ruleset_put(ruleset: rule->jump_ruleset); |
465 | |
466 | prestera_acl_ruleset_put(ruleset: rule->ruleset); |
467 | kfree(objp: rule); |
468 | } |
469 | |
470 | static void prestera_acl_ruleset_prio_update(struct prestera_acl_ruleset *ruleset, |
471 | u32 prio) |
472 | { |
473 | ruleset->prio.min = min(ruleset->prio.min, prio); |
474 | ruleset->prio.max = max(ruleset->prio.max, prio); |
475 | } |
476 | |
477 | int prestera_acl_rule_add(struct prestera_switch *sw, |
478 | struct prestera_acl_rule *rule) |
479 | { |
480 | int err; |
481 | struct prestera_acl_ruleset *ruleset = rule->ruleset; |
482 | struct prestera_flow_block *block = ruleset->ht_key.block; |
483 | |
484 | /* try to add rule to hash table first */ |
485 | err = rhashtable_insert_fast(ht: &ruleset->rule_ht, obj: &rule->ht_node, |
486 | params: prestera_acl_rule_ht_params); |
487 | if (err) |
488 | goto err_ht_insert; |
489 | |
490 | prestera_acl_rule_keymask_pcl_id_set(rule, pcl_id: ruleset->pcl_id); |
491 | rule->re_arg.vtcam_id = ruleset->vtcam_id; |
492 | rule->re_key.prio = rule->priority; |
493 | |
494 | rule->re = prestera_acl_rule_entry_find(acl: sw->acl, key: &rule->re_key); |
495 | err = WARN_ON(rule->re) ? -EEXIST : 0; |
496 | if (err) |
497 | goto err_rule_add; |
498 | |
499 | rule->re = prestera_acl_rule_entry_create(acl: sw->acl, key: &rule->re_key, |
500 | arg: &rule->re_arg); |
501 | err = !rule->re ? -EINVAL : 0; |
502 | if (err) |
503 | goto err_rule_add; |
504 | |
505 | /* bind the block (all ports) to chain index 0, rest of |
506 | * the chains are bound to goto action |
507 | */ |
508 | if (!ruleset->ht_key.chain_index && !ruleset->rule_count) { |
509 | err = prestera_acl_ruleset_block_bind(ruleset, block); |
510 | if (err) |
511 | goto err_acl_block_bind; |
512 | } |
513 | |
514 | list_add_tail(new: &rule->list, head: &sw->acl->rules); |
515 | ruleset->rule_count++; |
516 | prestera_acl_ruleset_prio_update(ruleset, prio: rule->priority); |
517 | return 0; |
518 | |
519 | err_acl_block_bind: |
520 | prestera_acl_rule_entry_destroy(acl: sw->acl, e: rule->re); |
521 | err_rule_add: |
522 | rule->re = NULL; |
523 | rhashtable_remove_fast(ht: &ruleset->rule_ht, obj: &rule->ht_node, |
524 | params: prestera_acl_rule_ht_params); |
525 | err_ht_insert: |
526 | return err; |
527 | } |
528 | |
529 | void prestera_acl_rule_del(struct prestera_switch *sw, |
530 | struct prestera_acl_rule *rule) |
531 | { |
532 | struct prestera_acl_ruleset *ruleset = rule->ruleset; |
533 | struct prestera_flow_block *block = ruleset->ht_key.block; |
534 | |
535 | rhashtable_remove_fast(ht: &ruleset->rule_ht, obj: &rule->ht_node, |
536 | params: prestera_acl_rule_ht_params); |
537 | ruleset->rule_count--; |
538 | list_del(entry: &rule->list); |
539 | |
540 | prestera_acl_rule_entry_destroy(acl: sw->acl, e: rule->re); |
541 | prestera_acl_ruleset_prio_refresh(acl: sw->acl, ruleset); |
542 | |
543 | /* unbind block (all ports) */ |
544 | if (!ruleset->ht_key.chain_index && !ruleset->rule_count) |
545 | prestera_acl_ruleset_block_unbind(ruleset, block); |
546 | } |
547 | |
548 | int prestera_acl_rule_get_stats(struct prestera_acl *acl, |
549 | struct prestera_acl_rule *rule, |
550 | u64 *packets, u64 *bytes, u64 *last_use) |
551 | { |
552 | u64 current_packets; |
553 | u64 current_bytes; |
554 | int err; |
555 | |
556 | err = prestera_counter_stats_get(counter: acl->sw->counter, |
557 | block: rule->re->counter.block, |
558 | counter_id: rule->re->counter.id, |
559 | packets: ¤t_packets, bytes: ¤t_bytes); |
560 | if (err) |
561 | return err; |
562 | |
563 | *packets = current_packets; |
564 | *bytes = current_bytes; |
565 | *last_use = jiffies; |
566 | |
567 | return 0; |
568 | } |
569 | |
570 | struct prestera_acl_rule_entry * |
571 | prestera_acl_rule_entry_find(struct prestera_acl *acl, |
572 | struct prestera_acl_rule_entry_key *key) |
573 | { |
574 | return rhashtable_lookup_fast(ht: &acl->acl_rule_entry_ht, key, |
575 | params: __prestera_acl_rule_entry_ht_params); |
576 | } |
577 | |
578 | static int __prestera_acl_rule_entry2hw_del(struct prestera_switch *sw, |
579 | struct prestera_acl_rule_entry *e) |
580 | { |
581 | return prestera_hw_vtcam_rule_del(sw, vtcam_id: e->vtcam_id, rule_id: e->hw_id); |
582 | } |
583 | |
584 | static int __prestera_acl_rule_entry2hw_add(struct prestera_switch *sw, |
585 | struct prestera_acl_rule_entry *e) |
586 | { |
587 | struct prestera_acl_hw_action_info act_hw[PRESTERA_ACL_RULE_ACTION_MAX]; |
588 | int act_num; |
589 | |
590 | memset(&act_hw, 0, sizeof(act_hw)); |
591 | act_num = 0; |
592 | |
593 | /* accept */ |
594 | if (e->accept.valid) { |
595 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_ACCEPT; |
596 | act_num++; |
597 | } |
598 | /* drop */ |
599 | if (e->drop.valid) { |
600 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_DROP; |
601 | act_num++; |
602 | } |
603 | /* trap */ |
604 | if (e->trap.valid) { |
605 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_TRAP; |
606 | act_num++; |
607 | } |
608 | /* police */ |
609 | if (e->police.valid) { |
610 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_POLICE; |
611 | act_hw[act_num].police = e->police.i; |
612 | act_num++; |
613 | } |
614 | /* jump */ |
615 | if (e->jump.valid) { |
616 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_JUMP; |
617 | act_hw[act_num].jump = e->jump.i; |
618 | act_num++; |
619 | } |
620 | /* counter */ |
621 | if (e->counter.block) { |
622 | act_hw[act_num].id = PRESTERA_ACL_RULE_ACTION_COUNT; |
623 | act_hw[act_num].count.id = e->counter.id; |
624 | act_num++; |
625 | } |
626 | |
627 | return prestera_hw_vtcam_rule_add(sw, vtcam_id: e->vtcam_id, prio: e->key.prio, |
628 | key: e->key.match.key, keymask: e->key.match.mask, |
629 | act: act_hw, n_act: act_num, rule_id: &e->hw_id); |
630 | } |
631 | |
632 | static void |
633 | __prestera_acl_rule_entry_act_destruct(struct prestera_switch *sw, |
634 | struct prestera_acl_rule_entry *e) |
635 | { |
636 | /* counter */ |
637 | prestera_counter_put(counter: sw->counter, block: e->counter.block, counter_id: e->counter.id); |
638 | /* police */ |
639 | if (e->police.valid) |
640 | prestera_hw_policer_release(sw, policer_id: e->police.i.id); |
641 | } |
642 | |
643 | void prestera_acl_rule_entry_destroy(struct prestera_acl *acl, |
644 | struct prestera_acl_rule_entry *e) |
645 | { |
646 | int ret; |
647 | |
648 | rhashtable_remove_fast(ht: &acl->acl_rule_entry_ht, obj: &e->ht_node, |
649 | params: __prestera_acl_rule_entry_ht_params); |
650 | |
651 | ret = __prestera_acl_rule_entry2hw_del(sw: acl->sw, e); |
652 | WARN_ON(ret && ret != -ENODEV); |
653 | |
654 | __prestera_acl_rule_entry_act_destruct(sw: acl->sw, e); |
655 | kfree(objp: e); |
656 | } |
657 | |
658 | static int |
659 | __prestera_acl_rule_entry_act_construct(struct prestera_switch *sw, |
660 | struct prestera_acl_rule_entry *e, |
661 | struct prestera_acl_rule_entry_arg *arg) |
662 | { |
663 | int err; |
664 | |
665 | /* accept */ |
666 | e->accept.valid = arg->accept.valid; |
667 | /* drop */ |
668 | e->drop.valid = arg->drop.valid; |
669 | /* trap */ |
670 | e->trap.valid = arg->trap.valid; |
671 | /* jump */ |
672 | e->jump.valid = arg->jump.valid; |
673 | e->jump.i = arg->jump.i; |
674 | /* police */ |
675 | if (arg->police.valid) { |
676 | u8 type = arg->police.ingress ? PRESTERA_POLICER_TYPE_INGRESS : |
677 | PRESTERA_POLICER_TYPE_EGRESS; |
678 | |
679 | err = prestera_hw_policer_create(sw, type, policer_id: &e->police.i.id); |
680 | if (err) |
681 | goto err_out; |
682 | |
683 | err = prestera_hw_policer_sr_tcm_set(sw, policer_id: e->police.i.id, |
684 | cir: arg->police.rate, |
685 | cbs: arg->police.burst); |
686 | if (err) { |
687 | prestera_hw_policer_release(sw, policer_id: e->police.i.id); |
688 | goto err_out; |
689 | } |
690 | e->police.valid = arg->police.valid; |
691 | } |
692 | /* counter */ |
693 | if (arg->count.valid) { |
694 | err = prestera_counter_get(counter: sw->counter, client: arg->count.client, |
695 | block: &e->counter.block, |
696 | counter_id: &e->counter.id); |
697 | if (err) |
698 | goto err_out; |
699 | } |
700 | |
701 | return 0; |
702 | |
703 | err_out: |
704 | __prestera_acl_rule_entry_act_destruct(sw, e); |
705 | return -EINVAL; |
706 | } |
707 | |
708 | struct prestera_acl_rule_entry * |
709 | prestera_acl_rule_entry_create(struct prestera_acl *acl, |
710 | struct prestera_acl_rule_entry_key *key, |
711 | struct prestera_acl_rule_entry_arg *arg) |
712 | { |
713 | struct prestera_acl_rule_entry *e; |
714 | int err; |
715 | |
716 | e = kzalloc(size: sizeof(*e), GFP_KERNEL); |
717 | if (!e) |
718 | goto err_kzalloc; |
719 | |
720 | memcpy(&e->key, key, sizeof(*key)); |
721 | e->vtcam_id = arg->vtcam_id; |
722 | err = __prestera_acl_rule_entry_act_construct(sw: acl->sw, e, arg); |
723 | if (err) |
724 | goto err_act_construct; |
725 | |
726 | err = __prestera_acl_rule_entry2hw_add(sw: acl->sw, e); |
727 | if (err) |
728 | goto err_hw_add; |
729 | |
730 | err = rhashtable_insert_fast(ht: &acl->acl_rule_entry_ht, obj: &e->ht_node, |
731 | params: __prestera_acl_rule_entry_ht_params); |
732 | if (err) |
733 | goto err_ht_insert; |
734 | |
735 | return e; |
736 | |
737 | err_ht_insert: |
738 | WARN_ON(__prestera_acl_rule_entry2hw_del(acl->sw, e)); |
739 | err_hw_add: |
740 | __prestera_acl_rule_entry_act_destruct(sw: acl->sw, e); |
741 | err_act_construct: |
742 | kfree(objp: e); |
743 | err_kzalloc: |
744 | return NULL; |
745 | } |
746 | |
747 | static int __prestera_acl_vtcam_id_try_fit(struct prestera_acl *acl, u8 lookup, |
748 | void *keymask, u32 *vtcam_id) |
749 | { |
750 | struct prestera_acl_vtcam *vtcam; |
751 | int i; |
752 | |
753 | list_for_each_entry(vtcam, &acl->vtcam_list, list) { |
754 | if (lookup != vtcam->lookup) |
755 | continue; |
756 | |
757 | if (!keymask && !vtcam->is_keymask_set) |
758 | goto vtcam_found; |
759 | |
760 | if (!(keymask && vtcam->is_keymask_set)) |
761 | continue; |
762 | |
763 | /* try to fit with vtcam keymask */ |
764 | for (i = 0; i < __PRESTERA_ACL_RULE_MATCH_TYPE_MAX; i++) { |
765 | __be32 __keymask = ((__be32 *)keymask)[i]; |
766 | |
767 | if (!__keymask) |
768 | /* vtcam keymask in not interested */ |
769 | continue; |
770 | |
771 | if (__keymask & ~vtcam->keymask[i]) |
772 | /* keymask does not fit the vtcam keymask */ |
773 | break; |
774 | } |
775 | |
776 | if (i == __PRESTERA_ACL_RULE_MATCH_TYPE_MAX) |
777 | /* keymask fits vtcam keymask, return it */ |
778 | goto vtcam_found; |
779 | } |
780 | |
781 | /* nothing is found */ |
782 | return -ENOENT; |
783 | |
784 | vtcam_found: |
785 | refcount_inc(r: &vtcam->refcount); |
786 | *vtcam_id = vtcam->id; |
787 | return 0; |
788 | } |
789 | |
790 | int prestera_acl_vtcam_id_get(struct prestera_acl *acl, u8 lookup, u8 dir, |
791 | void *keymask, u32 *vtcam_id) |
792 | { |
793 | struct prestera_acl_vtcam *vtcam; |
794 | u32 new_vtcam_id; |
795 | int err; |
796 | |
797 | /* find the vtcam that suits keymask. We do not expect to have |
798 | * a big number of vtcams, so, the list type for vtcam list is |
799 | * fine for now |
800 | */ |
801 | list_for_each_entry(vtcam, &acl->vtcam_list, list) { |
802 | if (lookup != vtcam->lookup || |
803 | dir != vtcam->direction) |
804 | continue; |
805 | |
806 | if (!keymask && !vtcam->is_keymask_set) { |
807 | refcount_inc(r: &vtcam->refcount); |
808 | goto vtcam_found; |
809 | } |
810 | |
811 | if (keymask && vtcam->is_keymask_set && |
812 | !memcmp(p: keymask, q: vtcam->keymask, size: sizeof(vtcam->keymask))) { |
813 | refcount_inc(r: &vtcam->refcount); |
814 | goto vtcam_found; |
815 | } |
816 | } |
817 | |
818 | /* vtcam not found, try to create new one */ |
819 | vtcam = kzalloc(size: sizeof(*vtcam), GFP_KERNEL); |
820 | if (!vtcam) |
821 | return -ENOMEM; |
822 | |
823 | err = prestera_hw_vtcam_create(sw: acl->sw, lookup, keymask, vtcam_id: &new_vtcam_id, |
824 | direction: dir); |
825 | if (err) { |
826 | kfree(objp: vtcam); |
827 | |
828 | /* cannot create new, try to fit into existing vtcam */ |
829 | if (__prestera_acl_vtcam_id_try_fit(acl, lookup, |
830 | keymask, vtcam_id: &new_vtcam_id)) |
831 | return err; |
832 | |
833 | *vtcam_id = new_vtcam_id; |
834 | return 0; |
835 | } |
836 | |
837 | vtcam->direction = dir; |
838 | vtcam->id = new_vtcam_id; |
839 | vtcam->lookup = lookup; |
840 | if (keymask) { |
841 | memcpy(vtcam->keymask, keymask, sizeof(vtcam->keymask)); |
842 | vtcam->is_keymask_set = true; |
843 | } |
844 | refcount_set(r: &vtcam->refcount, n: 1); |
845 | list_add_rcu(new: &vtcam->list, head: &acl->vtcam_list); |
846 | |
847 | vtcam_found: |
848 | *vtcam_id = vtcam->id; |
849 | return 0; |
850 | } |
851 | |
852 | int prestera_acl_vtcam_id_put(struct prestera_acl *acl, u32 vtcam_id) |
853 | { |
854 | struct prestera_acl_vtcam *vtcam; |
855 | int err; |
856 | |
857 | list_for_each_entry(vtcam, &acl->vtcam_list, list) { |
858 | if (vtcam_id != vtcam->id) |
859 | continue; |
860 | |
861 | if (!refcount_dec_and_test(r: &vtcam->refcount)) |
862 | return 0; |
863 | |
864 | err = prestera_hw_vtcam_destroy(sw: acl->sw, vtcam_id: vtcam->id); |
865 | if (err && err != -ENODEV) { |
866 | refcount_set(r: &vtcam->refcount, n: 1); |
867 | return err; |
868 | } |
869 | |
870 | list_del(entry: &vtcam->list); |
871 | kfree(objp: vtcam); |
872 | return 0; |
873 | } |
874 | |
875 | return -ENOENT; |
876 | } |
877 | |
878 | int prestera_acl_init(struct prestera_switch *sw) |
879 | { |
880 | struct prestera_acl *acl; |
881 | int err; |
882 | |
883 | acl = kzalloc(size: sizeof(*acl), GFP_KERNEL); |
884 | if (!acl) |
885 | return -ENOMEM; |
886 | |
887 | acl->sw = sw; |
888 | INIT_LIST_HEAD(list: &acl->rules); |
889 | INIT_LIST_HEAD(list: &acl->vtcam_list); |
890 | idr_init(idr: &acl->uid); |
891 | |
892 | err = rhashtable_init(ht: &acl->acl_rule_entry_ht, |
893 | params: &__prestera_acl_rule_entry_ht_params); |
894 | if (err) |
895 | goto err_acl_rule_entry_ht_init; |
896 | |
897 | err = rhashtable_init(ht: &acl->ruleset_ht, |
898 | params: &prestera_acl_ruleset_ht_params); |
899 | if (err) |
900 | goto err_ruleset_ht_init; |
901 | |
902 | sw->acl = acl; |
903 | |
904 | return 0; |
905 | |
906 | err_ruleset_ht_init: |
907 | rhashtable_destroy(ht: &acl->acl_rule_entry_ht); |
908 | err_acl_rule_entry_ht_init: |
909 | kfree(objp: acl); |
910 | return err; |
911 | } |
912 | |
913 | void prestera_acl_fini(struct prestera_switch *sw) |
914 | { |
915 | struct prestera_acl *acl = sw->acl; |
916 | |
917 | WARN_ON(!idr_is_empty(&acl->uid)); |
918 | idr_destroy(&acl->uid); |
919 | |
920 | WARN_ON(!list_empty(&acl->vtcam_list)); |
921 | WARN_ON(!list_empty(&acl->rules)); |
922 | |
923 | rhashtable_destroy(ht: &acl->ruleset_ht); |
924 | rhashtable_destroy(ht: &acl->acl_rule_entry_ht); |
925 | |
926 | kfree(objp: acl); |
927 | } |
928 | |