1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * DPAA2 Ethernet Switch flower support |
4 | * |
5 | * Copyright 2021 NXP |
6 | * |
7 | */ |
8 | |
9 | #include "dpaa2-switch.h" |
10 | |
11 | static int dpaa2_switch_flower_parse_key(struct flow_cls_offload *cls, |
12 | struct dpsw_acl_key *acl_key) |
13 | { |
14 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
15 | struct flow_dissector *dissector = rule->match.dissector; |
16 | struct netlink_ext_ack *extack = cls->common.extack; |
17 | struct dpsw_acl_fields *acl_h, *acl_m; |
18 | |
19 | if (dissector->used_keys & |
20 | ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
21 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | |
22 | BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
23 | BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) | |
24 | BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) | |
25 | BIT_ULL(FLOW_DISSECTOR_KEY_IP) | |
26 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | |
27 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS))) { |
28 | NL_SET_ERR_MSG_MOD(extack, |
29 | "Unsupported keys used" ); |
30 | return -EOPNOTSUPP; |
31 | } |
32 | |
33 | acl_h = &acl_key->match; |
34 | acl_m = &acl_key->mask; |
35 | |
36 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_BASIC)) { |
37 | struct flow_match_basic match; |
38 | |
39 | flow_rule_match_basic(rule, out: &match); |
40 | acl_h->l3_protocol = match.key->ip_proto; |
41 | acl_h->l2_ether_type = be16_to_cpu(match.key->n_proto); |
42 | acl_m->l3_protocol = match.mask->ip_proto; |
43 | acl_m->l2_ether_type = be16_to_cpu(match.mask->n_proto); |
44 | } |
45 | |
46 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
47 | struct flow_match_eth_addrs match; |
48 | |
49 | flow_rule_match_eth_addrs(rule, out: &match); |
50 | ether_addr_copy(dst: acl_h->l2_dest_mac, src: &match.key->dst[0]); |
51 | ether_addr_copy(dst: acl_h->l2_source_mac, src: &match.key->src[0]); |
52 | ether_addr_copy(dst: acl_m->l2_dest_mac, src: &match.mask->dst[0]); |
53 | ether_addr_copy(dst: acl_m->l2_source_mac, src: &match.mask->src[0]); |
54 | } |
55 | |
56 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_VLAN)) { |
57 | struct flow_match_vlan match; |
58 | |
59 | flow_rule_match_vlan(rule, out: &match); |
60 | acl_h->l2_vlan_id = match.key->vlan_id; |
61 | acl_h->l2_tpid = be16_to_cpu(match.key->vlan_tpid); |
62 | acl_h->l2_pcp_dei = match.key->vlan_priority << 1 | |
63 | match.key->vlan_dei; |
64 | |
65 | acl_m->l2_vlan_id = match.mask->vlan_id; |
66 | acl_m->l2_tpid = be16_to_cpu(match.mask->vlan_tpid); |
67 | acl_m->l2_pcp_dei = match.mask->vlan_priority << 1 | |
68 | match.mask->vlan_dei; |
69 | } |
70 | |
71 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IPV4_ADDRS)) { |
72 | struct flow_match_ipv4_addrs match; |
73 | |
74 | flow_rule_match_ipv4_addrs(rule, out: &match); |
75 | acl_h->l3_source_ip = be32_to_cpu(match.key->src); |
76 | acl_h->l3_dest_ip = be32_to_cpu(match.key->dst); |
77 | acl_m->l3_source_ip = be32_to_cpu(match.mask->src); |
78 | acl_m->l3_dest_ip = be32_to_cpu(match.mask->dst); |
79 | } |
80 | |
81 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_PORTS)) { |
82 | struct flow_match_ports match; |
83 | |
84 | flow_rule_match_ports(rule, out: &match); |
85 | acl_h->l4_source_port = be16_to_cpu(match.key->src); |
86 | acl_h->l4_dest_port = be16_to_cpu(match.key->dst); |
87 | acl_m->l4_source_port = be16_to_cpu(match.mask->src); |
88 | acl_m->l4_dest_port = be16_to_cpu(match.mask->dst); |
89 | } |
90 | |
91 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IP)) { |
92 | struct flow_match_ip match; |
93 | |
94 | flow_rule_match_ip(rule, out: &match); |
95 | if (match.mask->ttl != 0) { |
96 | NL_SET_ERR_MSG_MOD(extack, |
97 | "Matching on TTL not supported" ); |
98 | return -EOPNOTSUPP; |
99 | } |
100 | |
101 | if ((match.mask->tos & 0x3) != 0) { |
102 | NL_SET_ERR_MSG_MOD(extack, |
103 | "Matching on ECN not supported, only DSCP" ); |
104 | return -EOPNOTSUPP; |
105 | } |
106 | |
107 | acl_h->l3_dscp = match.key->tos >> 2; |
108 | acl_m->l3_dscp = match.mask->tos >> 2; |
109 | } |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | int dpaa2_switch_acl_entry_add(struct dpaa2_switch_filter_block *filter_block, |
115 | struct dpaa2_switch_acl_entry *entry) |
116 | { |
117 | struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg; |
118 | struct ethsw_core *ethsw = filter_block->ethsw; |
119 | struct dpsw_acl_key *acl_key = &entry->key; |
120 | struct device *dev = ethsw->dev; |
121 | u8 *cmd_buff; |
122 | int err; |
123 | |
124 | cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL); |
125 | if (!cmd_buff) |
126 | return -ENOMEM; |
127 | |
128 | dpsw_acl_prepare_entry_cfg(key: acl_key, entry_cfg_buf: cmd_buff); |
129 | |
130 | acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff, |
131 | DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, |
132 | DMA_TO_DEVICE); |
133 | if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) { |
134 | dev_err(dev, "DMA mapping failed\n" ); |
135 | kfree(objp: cmd_buff); |
136 | return -EFAULT; |
137 | } |
138 | |
139 | err = dpsw_acl_add_entry(mc_io: ethsw->mc_io, cmd_flags: 0, token: ethsw->dpsw_handle, |
140 | acl_id: filter_block->acl_id, cfg: acl_entry_cfg); |
141 | |
142 | dma_unmap_single(dev, acl_entry_cfg->key_iova, |
143 | DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, |
144 | DMA_TO_DEVICE); |
145 | if (err) { |
146 | dev_err(dev, "dpsw_acl_add_entry() failed %d\n" , err); |
147 | kfree(objp: cmd_buff); |
148 | return err; |
149 | } |
150 | |
151 | kfree(objp: cmd_buff); |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | static int |
157 | dpaa2_switch_acl_entry_remove(struct dpaa2_switch_filter_block *block, |
158 | struct dpaa2_switch_acl_entry *entry) |
159 | { |
160 | struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg; |
161 | struct dpsw_acl_key *acl_key = &entry->key; |
162 | struct ethsw_core *ethsw = block->ethsw; |
163 | struct device *dev = ethsw->dev; |
164 | u8 *cmd_buff; |
165 | int err; |
166 | |
167 | cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL); |
168 | if (!cmd_buff) |
169 | return -ENOMEM; |
170 | |
171 | dpsw_acl_prepare_entry_cfg(key: acl_key, entry_cfg_buf: cmd_buff); |
172 | |
173 | acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff, |
174 | DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, |
175 | DMA_TO_DEVICE); |
176 | if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) { |
177 | dev_err(dev, "DMA mapping failed\n" ); |
178 | kfree(objp: cmd_buff); |
179 | return -EFAULT; |
180 | } |
181 | |
182 | err = dpsw_acl_remove_entry(mc_io: ethsw->mc_io, cmd_flags: 0, token: ethsw->dpsw_handle, |
183 | acl_id: block->acl_id, cfg: acl_entry_cfg); |
184 | |
185 | dma_unmap_single(dev, acl_entry_cfg->key_iova, |
186 | DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, DMA_TO_DEVICE); |
187 | if (err) { |
188 | dev_err(dev, "dpsw_acl_remove_entry() failed %d\n" , err); |
189 | kfree(objp: cmd_buff); |
190 | return err; |
191 | } |
192 | |
193 | kfree(objp: cmd_buff); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static int |
199 | dpaa2_switch_acl_entry_add_to_list(struct dpaa2_switch_filter_block *block, |
200 | struct dpaa2_switch_acl_entry *entry) |
201 | { |
202 | struct dpaa2_switch_acl_entry *tmp; |
203 | struct list_head *pos, *n; |
204 | int index = 0; |
205 | |
206 | if (list_empty(head: &block->acl_entries)) { |
207 | list_add(new: &entry->list, head: &block->acl_entries); |
208 | return index; |
209 | } |
210 | |
211 | list_for_each_safe(pos, n, &block->acl_entries) { |
212 | tmp = list_entry(pos, struct dpaa2_switch_acl_entry, list); |
213 | if (entry->prio < tmp->prio) |
214 | break; |
215 | index++; |
216 | } |
217 | list_add(new: &entry->list, head: pos->prev); |
218 | return index; |
219 | } |
220 | |
221 | static struct dpaa2_switch_acl_entry* |
222 | dpaa2_switch_acl_entry_get_by_index(struct dpaa2_switch_filter_block *block, |
223 | int index) |
224 | { |
225 | struct dpaa2_switch_acl_entry *tmp; |
226 | int i = 0; |
227 | |
228 | list_for_each_entry(tmp, &block->acl_entries, list) { |
229 | if (i == index) |
230 | return tmp; |
231 | ++i; |
232 | } |
233 | |
234 | return NULL; |
235 | } |
236 | |
237 | static int |
238 | dpaa2_switch_acl_entry_set_precedence(struct dpaa2_switch_filter_block *block, |
239 | struct dpaa2_switch_acl_entry *entry, |
240 | int precedence) |
241 | { |
242 | int err; |
243 | |
244 | err = dpaa2_switch_acl_entry_remove(block, entry); |
245 | if (err) |
246 | return err; |
247 | |
248 | entry->cfg.precedence = precedence; |
249 | return dpaa2_switch_acl_entry_add(filter_block: block, entry); |
250 | } |
251 | |
252 | static int |
253 | dpaa2_switch_acl_tbl_add_entry(struct dpaa2_switch_filter_block *block, |
254 | struct dpaa2_switch_acl_entry *entry) |
255 | { |
256 | struct dpaa2_switch_acl_entry *tmp; |
257 | int index, i, precedence, err; |
258 | |
259 | /* Add the new ACL entry to the linked list and get its index */ |
260 | index = dpaa2_switch_acl_entry_add_to_list(block, entry); |
261 | |
262 | /* Move up in priority the ACL entries to make space |
263 | * for the new filter. |
264 | */ |
265 | precedence = DPAA2_ETHSW_PORT_MAX_ACL_ENTRIES - block->num_acl_rules - 1; |
266 | for (i = 0; i < index; i++) { |
267 | tmp = dpaa2_switch_acl_entry_get_by_index(block, index: i); |
268 | |
269 | err = dpaa2_switch_acl_entry_set_precedence(block, entry: tmp, |
270 | precedence); |
271 | if (err) |
272 | return err; |
273 | |
274 | precedence++; |
275 | } |
276 | |
277 | /* Add the new entry to hardware */ |
278 | entry->cfg.precedence = precedence; |
279 | err = dpaa2_switch_acl_entry_add(filter_block: block, entry); |
280 | block->num_acl_rules++; |
281 | |
282 | return err; |
283 | } |
284 | |
285 | static struct dpaa2_switch_acl_entry * |
286 | dpaa2_switch_acl_tbl_find_entry_by_cookie(struct dpaa2_switch_filter_block *block, |
287 | unsigned long cookie) |
288 | { |
289 | struct dpaa2_switch_acl_entry *tmp, *n; |
290 | |
291 | list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { |
292 | if (tmp->cookie == cookie) |
293 | return tmp; |
294 | } |
295 | return NULL; |
296 | } |
297 | |
298 | static int |
299 | dpaa2_switch_acl_entry_get_index(struct dpaa2_switch_filter_block *block, |
300 | struct dpaa2_switch_acl_entry *entry) |
301 | { |
302 | struct dpaa2_switch_acl_entry *tmp, *n; |
303 | int index = 0; |
304 | |
305 | list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { |
306 | if (tmp->cookie == entry->cookie) |
307 | return index; |
308 | index++; |
309 | } |
310 | return -ENOENT; |
311 | } |
312 | |
313 | static struct dpaa2_switch_mirror_entry * |
314 | dpaa2_switch_mirror_find_entry_by_cookie(struct dpaa2_switch_filter_block *block, |
315 | unsigned long cookie) |
316 | { |
317 | struct dpaa2_switch_mirror_entry *tmp, *n; |
318 | |
319 | list_for_each_entry_safe(tmp, n, &block->mirror_entries, list) { |
320 | if (tmp->cookie == cookie) |
321 | return tmp; |
322 | } |
323 | return NULL; |
324 | } |
325 | |
326 | static int |
327 | dpaa2_switch_acl_tbl_remove_entry(struct dpaa2_switch_filter_block *block, |
328 | struct dpaa2_switch_acl_entry *entry) |
329 | { |
330 | struct dpaa2_switch_acl_entry *tmp; |
331 | int index, i, precedence, err; |
332 | |
333 | index = dpaa2_switch_acl_entry_get_index(block, entry); |
334 | |
335 | /* Remove from hardware the ACL entry */ |
336 | err = dpaa2_switch_acl_entry_remove(block, entry); |
337 | if (err) |
338 | return err; |
339 | |
340 | block->num_acl_rules--; |
341 | |
342 | /* Remove it from the list also */ |
343 | list_del(entry: &entry->list); |
344 | |
345 | /* Move down in priority the entries over the deleted one */ |
346 | precedence = entry->cfg.precedence; |
347 | for (i = index - 1; i >= 0; i--) { |
348 | tmp = dpaa2_switch_acl_entry_get_by_index(block, index: i); |
349 | err = dpaa2_switch_acl_entry_set_precedence(block, entry: tmp, |
350 | precedence); |
351 | if (err) |
352 | return err; |
353 | |
354 | precedence--; |
355 | } |
356 | |
357 | kfree(objp: entry); |
358 | |
359 | return 0; |
360 | } |
361 | |
362 | static int dpaa2_switch_tc_parse_action_acl(struct ethsw_core *ethsw, |
363 | struct flow_action_entry *cls_act, |
364 | struct dpsw_acl_result *dpsw_act, |
365 | struct netlink_ext_ack *extack) |
366 | { |
367 | int err = 0; |
368 | |
369 | switch (cls_act->id) { |
370 | case FLOW_ACTION_TRAP: |
371 | dpsw_act->action = DPSW_ACL_ACTION_REDIRECT_TO_CTRL_IF; |
372 | break; |
373 | case FLOW_ACTION_REDIRECT: |
374 | if (!dpaa2_switch_port_dev_check(netdev: cls_act->dev)) { |
375 | NL_SET_ERR_MSG_MOD(extack, |
376 | "Destination not a DPAA2 switch port" ); |
377 | return -EOPNOTSUPP; |
378 | } |
379 | |
380 | dpsw_act->if_id = dpaa2_switch_get_index(ethsw, netdev: cls_act->dev); |
381 | dpsw_act->action = DPSW_ACL_ACTION_REDIRECT; |
382 | break; |
383 | case FLOW_ACTION_DROP: |
384 | dpsw_act->action = DPSW_ACL_ACTION_DROP; |
385 | break; |
386 | default: |
387 | NL_SET_ERR_MSG_MOD(extack, |
388 | "Action not supported" ); |
389 | err = -EOPNOTSUPP; |
390 | goto out; |
391 | } |
392 | |
393 | out: |
394 | return err; |
395 | } |
396 | |
397 | static int |
398 | dpaa2_switch_block_add_mirror(struct dpaa2_switch_filter_block *block, |
399 | struct dpaa2_switch_mirror_entry *entry, |
400 | u16 to, struct netlink_ext_ack *extack) |
401 | { |
402 | unsigned long block_ports = block->ports; |
403 | struct ethsw_core *ethsw = block->ethsw; |
404 | struct ethsw_port_priv *port_priv; |
405 | unsigned long ports_added = 0; |
406 | u16 vlan = entry->cfg.vlan_id; |
407 | bool mirror_port_enabled; |
408 | int err, port; |
409 | |
410 | /* Setup the mirroring port */ |
411 | mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); |
412 | if (!mirror_port_enabled) { |
413 | err = dpsw_set_reflection_if(mc_io: ethsw->mc_io, cmd_flags: 0, |
414 | token: ethsw->dpsw_handle, if_id: to); |
415 | if (err) |
416 | return err; |
417 | ethsw->mirror_port = to; |
418 | } |
419 | |
420 | /* Setup the same egress mirroring configuration on all the switch |
421 | * ports that share the same filter block. |
422 | */ |
423 | for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs) { |
424 | port_priv = ethsw->ports[port]; |
425 | |
426 | /* We cannot add a per VLAN mirroring rule if the VLAN in |
427 | * question is not installed on the switch port. |
428 | */ |
429 | if (entry->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN && |
430 | !(port_priv->vlans[vlan] & ETHSW_VLAN_MEMBER)) { |
431 | NL_SET_ERR_MSG(extack, |
432 | "VLAN must be installed on the switch port" ); |
433 | err = -EINVAL; |
434 | goto err_remove_filters; |
435 | } |
436 | |
437 | err = dpsw_if_add_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, |
438 | token: ethsw->dpsw_handle, |
439 | if_id: port, cfg: &entry->cfg); |
440 | if (err) |
441 | goto err_remove_filters; |
442 | |
443 | ports_added |= BIT(port); |
444 | } |
445 | |
446 | list_add(new: &entry->list, head: &block->mirror_entries); |
447 | |
448 | return 0; |
449 | |
450 | err_remove_filters: |
451 | for_each_set_bit(port, &ports_added, ethsw->sw_attr.num_ifs) { |
452 | dpsw_if_remove_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, token: ethsw->dpsw_handle, |
453 | if_id: port, cfg: &entry->cfg); |
454 | } |
455 | |
456 | if (!mirror_port_enabled) |
457 | ethsw->mirror_port = ethsw->sw_attr.num_ifs; |
458 | |
459 | return err; |
460 | } |
461 | |
462 | static int |
463 | dpaa2_switch_block_remove_mirror(struct dpaa2_switch_filter_block *block, |
464 | struct dpaa2_switch_mirror_entry *entry) |
465 | { |
466 | struct dpsw_reflection_cfg *cfg = &entry->cfg; |
467 | unsigned long block_ports = block->ports; |
468 | struct ethsw_core *ethsw = block->ethsw; |
469 | int port; |
470 | |
471 | /* Remove this mirroring configuration from all the ports belonging to |
472 | * the filter block. |
473 | */ |
474 | for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs) |
475 | dpsw_if_remove_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, token: ethsw->dpsw_handle, |
476 | if_id: port, cfg); |
477 | |
478 | /* Also remove it from the list of mirror filters */ |
479 | list_del(entry: &entry->list); |
480 | kfree(objp: entry); |
481 | |
482 | /* If this was the last mirror filter, then unset the mirror port */ |
483 | if (list_empty(head: &block->mirror_entries)) |
484 | ethsw->mirror_port = ethsw->sw_attr.num_ifs; |
485 | |
486 | return 0; |
487 | } |
488 | |
489 | static int |
490 | dpaa2_switch_cls_flower_replace_acl(struct dpaa2_switch_filter_block *block, |
491 | struct flow_cls_offload *cls) |
492 | { |
493 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
494 | struct netlink_ext_ack *extack = cls->common.extack; |
495 | struct dpaa2_switch_acl_entry *acl_entry; |
496 | struct ethsw_core *ethsw = block->ethsw; |
497 | struct flow_action_entry *act; |
498 | int err; |
499 | |
500 | if (dpaa2_switch_acl_tbl_is_full(filter_block: block)) { |
501 | NL_SET_ERR_MSG(extack, "Maximum filter capacity reached" ); |
502 | return -ENOMEM; |
503 | } |
504 | |
505 | acl_entry = kzalloc(size: sizeof(*acl_entry), GFP_KERNEL); |
506 | if (!acl_entry) |
507 | return -ENOMEM; |
508 | |
509 | err = dpaa2_switch_flower_parse_key(cls, acl_key: &acl_entry->key); |
510 | if (err) |
511 | goto free_acl_entry; |
512 | |
513 | act = &rule->action.entries[0]; |
514 | err = dpaa2_switch_tc_parse_action_acl(ethsw, cls_act: act, |
515 | dpsw_act: &acl_entry->cfg.result, extack); |
516 | if (err) |
517 | goto free_acl_entry; |
518 | |
519 | acl_entry->prio = cls->common.prio; |
520 | acl_entry->cookie = cls->cookie; |
521 | |
522 | err = dpaa2_switch_acl_tbl_add_entry(block, entry: acl_entry); |
523 | if (err) |
524 | goto free_acl_entry; |
525 | |
526 | return 0; |
527 | |
528 | free_acl_entry: |
529 | kfree(objp: acl_entry); |
530 | |
531 | return err; |
532 | } |
533 | |
534 | static int dpaa2_switch_flower_parse_mirror_key(struct flow_cls_offload *cls, |
535 | u16 *vlan) |
536 | { |
537 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
538 | struct flow_dissector *dissector = rule->match.dissector; |
539 | struct netlink_ext_ack *extack = cls->common.extack; |
540 | int ret = -EOPNOTSUPP; |
541 | |
542 | if (dissector->used_keys & |
543 | ~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
544 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | |
545 | BIT_ULL(FLOW_DISSECTOR_KEY_VLAN))) { |
546 | NL_SET_ERR_MSG_MOD(extack, |
547 | "Mirroring is supported only per VLAN" ); |
548 | return -EOPNOTSUPP; |
549 | } |
550 | |
551 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_VLAN)) { |
552 | struct flow_match_vlan match; |
553 | |
554 | flow_rule_match_vlan(rule, out: &match); |
555 | |
556 | if (match.mask->vlan_priority != 0 || |
557 | match.mask->vlan_dei != 0) { |
558 | NL_SET_ERR_MSG_MOD(extack, |
559 | "Only matching on VLAN ID supported" ); |
560 | return -EOPNOTSUPP; |
561 | } |
562 | |
563 | if (match.mask->vlan_id != 0xFFF) { |
564 | NL_SET_ERR_MSG_MOD(extack, |
565 | "Masked matching not supported" ); |
566 | return -EOPNOTSUPP; |
567 | } |
568 | |
569 | *vlan = (u16)match.key->vlan_id; |
570 | ret = 0; |
571 | } |
572 | |
573 | return ret; |
574 | } |
575 | |
576 | static int |
577 | dpaa2_switch_cls_flower_replace_mirror(struct dpaa2_switch_filter_block *block, |
578 | struct flow_cls_offload *cls) |
579 | { |
580 | struct netlink_ext_ack *extack = cls->common.extack; |
581 | struct dpaa2_switch_mirror_entry *mirror_entry; |
582 | struct ethsw_core *ethsw = block->ethsw; |
583 | struct dpaa2_switch_mirror_entry *tmp; |
584 | struct flow_action_entry *cls_act; |
585 | struct list_head *pos, *n; |
586 | bool mirror_port_enabled; |
587 | u16 if_id, vlan; |
588 | int err; |
589 | |
590 | mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); |
591 | cls_act = &cls->rule->action.entries[0]; |
592 | |
593 | /* Offload rules only when the destination is a DPAA2 switch port */ |
594 | if (!dpaa2_switch_port_dev_check(netdev: cls_act->dev)) { |
595 | NL_SET_ERR_MSG_MOD(extack, |
596 | "Destination not a DPAA2 switch port" ); |
597 | return -EOPNOTSUPP; |
598 | } |
599 | if_id = dpaa2_switch_get_index(ethsw, netdev: cls_act->dev); |
600 | |
601 | /* We have a single mirror port but can configure egress mirroring on |
602 | * all the other switch ports. We need to allow mirroring rules only |
603 | * when the destination port is the same. |
604 | */ |
605 | if (mirror_port_enabled && ethsw->mirror_port != if_id) { |
606 | NL_SET_ERR_MSG_MOD(extack, |
607 | "Multiple mirror ports not supported" ); |
608 | return -EBUSY; |
609 | } |
610 | |
611 | /* Parse the key */ |
612 | err = dpaa2_switch_flower_parse_mirror_key(cls, vlan: &vlan); |
613 | if (err) |
614 | return err; |
615 | |
616 | /* Make sure that we don't already have a mirror rule with the same |
617 | * configuration. |
618 | */ |
619 | list_for_each_safe(pos, n, &block->mirror_entries) { |
620 | tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list); |
621 | |
622 | if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN && |
623 | tmp->cfg.vlan_id == vlan) { |
624 | NL_SET_ERR_MSG_MOD(extack, |
625 | "VLAN mirror filter already installed" ); |
626 | return -EBUSY; |
627 | } |
628 | } |
629 | |
630 | mirror_entry = kzalloc(size: sizeof(*mirror_entry), GFP_KERNEL); |
631 | if (!mirror_entry) |
632 | return -ENOMEM; |
633 | |
634 | mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_VLAN; |
635 | mirror_entry->cfg.vlan_id = vlan; |
636 | mirror_entry->cookie = cls->cookie; |
637 | |
638 | return dpaa2_switch_block_add_mirror(block, entry: mirror_entry, to: if_id, |
639 | extack); |
640 | } |
641 | |
642 | int dpaa2_switch_cls_flower_replace(struct dpaa2_switch_filter_block *block, |
643 | struct flow_cls_offload *cls) |
644 | { |
645 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: cls); |
646 | struct netlink_ext_ack *extack = cls->common.extack; |
647 | struct flow_action_entry *act; |
648 | |
649 | if (!flow_offload_has_one_action(action: &rule->action)) { |
650 | NL_SET_ERR_MSG(extack, "Only singular actions are supported" ); |
651 | return -EOPNOTSUPP; |
652 | } |
653 | |
654 | act = &rule->action.entries[0]; |
655 | switch (act->id) { |
656 | case FLOW_ACTION_REDIRECT: |
657 | case FLOW_ACTION_TRAP: |
658 | case FLOW_ACTION_DROP: |
659 | return dpaa2_switch_cls_flower_replace_acl(block, cls); |
660 | case FLOW_ACTION_MIRRED: |
661 | return dpaa2_switch_cls_flower_replace_mirror(block, cls); |
662 | default: |
663 | NL_SET_ERR_MSG_MOD(extack, "Action not supported" ); |
664 | return -EOPNOTSUPP; |
665 | } |
666 | } |
667 | |
668 | int dpaa2_switch_cls_flower_destroy(struct dpaa2_switch_filter_block *block, |
669 | struct flow_cls_offload *cls) |
670 | { |
671 | struct dpaa2_switch_mirror_entry *mirror_entry; |
672 | struct dpaa2_switch_acl_entry *acl_entry; |
673 | |
674 | /* If this filter is a an ACL one, remove it */ |
675 | acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block, |
676 | cookie: cls->cookie); |
677 | if (acl_entry) |
678 | return dpaa2_switch_acl_tbl_remove_entry(block, entry: acl_entry); |
679 | |
680 | /* If not, then it has to be a mirror */ |
681 | mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block, |
682 | cookie: cls->cookie); |
683 | if (mirror_entry) |
684 | return dpaa2_switch_block_remove_mirror(block, |
685 | entry: mirror_entry); |
686 | |
687 | return 0; |
688 | } |
689 | |
690 | static int |
691 | dpaa2_switch_cls_matchall_replace_acl(struct dpaa2_switch_filter_block *block, |
692 | struct tc_cls_matchall_offload *cls) |
693 | { |
694 | struct netlink_ext_ack *extack = cls->common.extack; |
695 | struct ethsw_core *ethsw = block->ethsw; |
696 | struct dpaa2_switch_acl_entry *acl_entry; |
697 | struct flow_action_entry *act; |
698 | int err; |
699 | |
700 | if (dpaa2_switch_acl_tbl_is_full(filter_block: block)) { |
701 | NL_SET_ERR_MSG(extack, "Maximum filter capacity reached" ); |
702 | return -ENOMEM; |
703 | } |
704 | |
705 | acl_entry = kzalloc(size: sizeof(*acl_entry), GFP_KERNEL); |
706 | if (!acl_entry) |
707 | return -ENOMEM; |
708 | |
709 | act = &cls->rule->action.entries[0]; |
710 | err = dpaa2_switch_tc_parse_action_acl(ethsw, cls_act: act, |
711 | dpsw_act: &acl_entry->cfg.result, extack); |
712 | if (err) |
713 | goto free_acl_entry; |
714 | |
715 | acl_entry->prio = cls->common.prio; |
716 | acl_entry->cookie = cls->cookie; |
717 | |
718 | err = dpaa2_switch_acl_tbl_add_entry(block, entry: acl_entry); |
719 | if (err) |
720 | goto free_acl_entry; |
721 | |
722 | return 0; |
723 | |
724 | free_acl_entry: |
725 | kfree(objp: acl_entry); |
726 | |
727 | return err; |
728 | } |
729 | |
730 | static int |
731 | dpaa2_switch_cls_matchall_replace_mirror(struct dpaa2_switch_filter_block *block, |
732 | struct tc_cls_matchall_offload *cls) |
733 | { |
734 | struct netlink_ext_ack *extack = cls->common.extack; |
735 | struct dpaa2_switch_mirror_entry *mirror_entry; |
736 | struct ethsw_core *ethsw = block->ethsw; |
737 | struct dpaa2_switch_mirror_entry *tmp; |
738 | struct flow_action_entry *cls_act; |
739 | struct list_head *pos, *n; |
740 | bool mirror_port_enabled; |
741 | u16 if_id; |
742 | |
743 | mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); |
744 | cls_act = &cls->rule->action.entries[0]; |
745 | |
746 | /* Offload rules only when the destination is a DPAA2 switch port */ |
747 | if (!dpaa2_switch_port_dev_check(netdev: cls_act->dev)) { |
748 | NL_SET_ERR_MSG_MOD(extack, |
749 | "Destination not a DPAA2 switch port" ); |
750 | return -EOPNOTSUPP; |
751 | } |
752 | if_id = dpaa2_switch_get_index(ethsw, netdev: cls_act->dev); |
753 | |
754 | /* We have a single mirror port but can configure egress mirroring on |
755 | * all the other switch ports. We need to allow mirroring rules only |
756 | * when the destination port is the same. |
757 | */ |
758 | if (mirror_port_enabled && ethsw->mirror_port != if_id) { |
759 | NL_SET_ERR_MSG_MOD(extack, |
760 | "Multiple mirror ports not supported" ); |
761 | return -EBUSY; |
762 | } |
763 | |
764 | /* Make sure that we don't already have a mirror rule with the same |
765 | * configuration. One matchall rule per block is the maximum. |
766 | */ |
767 | list_for_each_safe(pos, n, &block->mirror_entries) { |
768 | tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list); |
769 | |
770 | if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_ALL) { |
771 | NL_SET_ERR_MSG_MOD(extack, |
772 | "Matchall mirror filter already installed" ); |
773 | return -EBUSY; |
774 | } |
775 | } |
776 | |
777 | mirror_entry = kzalloc(size: sizeof(*mirror_entry), GFP_KERNEL); |
778 | if (!mirror_entry) |
779 | return -ENOMEM; |
780 | |
781 | mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_ALL; |
782 | mirror_entry->cookie = cls->cookie; |
783 | |
784 | return dpaa2_switch_block_add_mirror(block, entry: mirror_entry, to: if_id, |
785 | extack); |
786 | } |
787 | |
788 | int dpaa2_switch_cls_matchall_replace(struct dpaa2_switch_filter_block *block, |
789 | struct tc_cls_matchall_offload *cls) |
790 | { |
791 | struct netlink_ext_ack *extack = cls->common.extack; |
792 | struct flow_action_entry *act; |
793 | |
794 | if (!flow_offload_has_one_action(action: &cls->rule->action)) { |
795 | NL_SET_ERR_MSG(extack, "Only singular actions are supported" ); |
796 | return -EOPNOTSUPP; |
797 | } |
798 | |
799 | act = &cls->rule->action.entries[0]; |
800 | switch (act->id) { |
801 | case FLOW_ACTION_REDIRECT: |
802 | case FLOW_ACTION_TRAP: |
803 | case FLOW_ACTION_DROP: |
804 | return dpaa2_switch_cls_matchall_replace_acl(block, cls); |
805 | case FLOW_ACTION_MIRRED: |
806 | return dpaa2_switch_cls_matchall_replace_mirror(block, cls); |
807 | default: |
808 | NL_SET_ERR_MSG_MOD(extack, "Action not supported" ); |
809 | return -EOPNOTSUPP; |
810 | } |
811 | } |
812 | |
813 | int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, |
814 | struct ethsw_port_priv *port_priv) |
815 | { |
816 | struct ethsw_core *ethsw = port_priv->ethsw_data; |
817 | struct dpaa2_switch_mirror_entry *tmp; |
818 | int err; |
819 | |
820 | list_for_each_entry(tmp, &block->mirror_entries, list) { |
821 | err = dpsw_if_add_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, |
822 | token: ethsw->dpsw_handle, |
823 | if_id: port_priv->idx, cfg: &tmp->cfg); |
824 | if (err) |
825 | goto unwind_add; |
826 | } |
827 | |
828 | return 0; |
829 | |
830 | unwind_add: |
831 | list_for_each_entry(tmp, &block->mirror_entries, list) |
832 | dpsw_if_remove_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, |
833 | token: ethsw->dpsw_handle, |
834 | if_id: port_priv->idx, cfg: &tmp->cfg); |
835 | |
836 | return err; |
837 | } |
838 | |
839 | int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, |
840 | struct ethsw_port_priv *port_priv) |
841 | { |
842 | struct ethsw_core *ethsw = port_priv->ethsw_data; |
843 | struct dpaa2_switch_mirror_entry *tmp; |
844 | int err; |
845 | |
846 | list_for_each_entry(tmp, &block->mirror_entries, list) { |
847 | err = dpsw_if_remove_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, |
848 | token: ethsw->dpsw_handle, |
849 | if_id: port_priv->idx, cfg: &tmp->cfg); |
850 | if (err) |
851 | goto unwind_remove; |
852 | } |
853 | |
854 | return 0; |
855 | |
856 | unwind_remove: |
857 | list_for_each_entry(tmp, &block->mirror_entries, list) |
858 | dpsw_if_add_reflection(mc_io: ethsw->mc_io, cmd_flags: 0, token: ethsw->dpsw_handle, |
859 | if_id: port_priv->idx, cfg: &tmp->cfg); |
860 | |
861 | return err; |
862 | } |
863 | |
864 | int dpaa2_switch_cls_matchall_destroy(struct dpaa2_switch_filter_block *block, |
865 | struct tc_cls_matchall_offload *cls) |
866 | { |
867 | struct dpaa2_switch_mirror_entry *mirror_entry; |
868 | struct dpaa2_switch_acl_entry *acl_entry; |
869 | |
870 | /* If this filter is a an ACL one, remove it */ |
871 | acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block, |
872 | cookie: cls->cookie); |
873 | if (acl_entry) |
874 | return dpaa2_switch_acl_tbl_remove_entry(block, |
875 | entry: acl_entry); |
876 | |
877 | /* If not, then it has to be a mirror */ |
878 | mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block, |
879 | cookie: cls->cookie); |
880 | if (mirror_entry) |
881 | return dpaa2_switch_block_remove_mirror(block, |
882 | entry: mirror_entry); |
883 | |
884 | return 0; |
885 | } |
886 | |