1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
---|---|
2 | /* Microsemi Ocelot Switch driver |
3 | * Copyright (c) 2019 Microsemi Corporation |
4 | */ |
5 | |
6 | #include <net/pkt_cls.h> |
7 | #include <net/tc_act/tc_gact.h> |
8 | #include <soc/mscc/ocelot_vcap.h> |
9 | #include "ocelot_police.h" |
10 | #include "ocelot_vcap.h" |
11 | |
12 | /* Arbitrarily chosen constants for encoding the VCAP block and lookup number |
13 | * into the chain number. This is UAPI. |
14 | */ |
15 | #define VCAP_BLOCK 10000 |
16 | #define VCAP_LOOKUP 1000 |
17 | #define VCAP_IS1_NUM_LOOKUPS 3 |
18 | #define VCAP_IS2_NUM_LOOKUPS 2 |
19 | #define VCAP_IS2_NUM_PAG 256 |
20 | #define VCAP_IS1_CHAIN(lookup) \ |
21 | (1 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP) |
22 | #define VCAP_IS2_CHAIN(lookup, pag) \ |
23 | (2 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP + (pag)) |
24 | /* PSFP chain and block ID */ |
25 | #define PSFP_BLOCK_ID OCELOT_NUM_VCAP_BLOCKS |
26 | #define OCELOT_PSFP_CHAIN (3 * VCAP_BLOCK) |
27 | |
28 | static int ocelot_chain_to_block(int chain, bool ingress) |
29 | { |
30 | int lookup, pag; |
31 | |
32 | if (!ingress) { |
33 | if (chain == 0) |
34 | return VCAP_ES0; |
35 | return -EOPNOTSUPP; |
36 | } |
37 | |
38 | /* Backwards compatibility with older, single-chain tc-flower |
39 | * offload support in Ocelot |
40 | */ |
41 | if (chain == 0) |
42 | return VCAP_IS2; |
43 | |
44 | for (lookup = 0; lookup < VCAP_IS1_NUM_LOOKUPS; lookup++) |
45 | if (chain == VCAP_IS1_CHAIN(lookup)) |
46 | return VCAP_IS1; |
47 | |
48 | for (lookup = 0; lookup < VCAP_IS2_NUM_LOOKUPS; lookup++) |
49 | for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) |
50 | if (chain == VCAP_IS2_CHAIN(lookup, pag)) |
51 | return VCAP_IS2; |
52 | |
53 | if (chain == OCELOT_PSFP_CHAIN) |
54 | return PSFP_BLOCK_ID; |
55 | |
56 | return -EOPNOTSUPP; |
57 | } |
58 | |
59 | /* Caller must ensure this is a valid IS1 or IS2 chain first, |
60 | * by calling ocelot_chain_to_block. |
61 | */ |
62 | static int ocelot_chain_to_lookup(int chain) |
63 | { |
64 | /* Backwards compatibility with older, single-chain tc-flower |
65 | * offload support in Ocelot |
66 | */ |
67 | if (chain == 0) |
68 | return 0; |
69 | |
70 | return (chain / VCAP_LOOKUP) % 10; |
71 | } |
72 | |
73 | /* Caller must ensure this is a valid IS2 chain first, |
74 | * by calling ocelot_chain_to_block. |
75 | */ |
76 | static int ocelot_chain_to_pag(int chain) |
77 | { |
78 | int lookup; |
79 | |
80 | /* Backwards compatibility with older, single-chain tc-flower |
81 | * offload support in Ocelot |
82 | */ |
83 | if (chain == 0) |
84 | return 0; |
85 | |
86 | lookup = ocelot_chain_to_lookup(chain); |
87 | |
88 | /* calculate PAG value as chain index relative to the first PAG */ |
89 | return chain - VCAP_IS2_CHAIN(lookup, 0); |
90 | } |
91 | |
92 | static bool ocelot_is_goto_target_valid(int goto_target, int chain, |
93 | bool ingress) |
94 | { |
95 | int pag; |
96 | |
97 | /* Can't offload GOTO in VCAP ES0 */ |
98 | if (!ingress) |
99 | return (goto_target < 0); |
100 | |
101 | /* Non-optional GOTOs */ |
102 | if (chain == 0) |
103 | /* VCAP IS1 can be skipped, either partially or completely */ |
104 | return (goto_target == VCAP_IS1_CHAIN(0) || |
105 | goto_target == VCAP_IS1_CHAIN(1) || |
106 | goto_target == VCAP_IS1_CHAIN(2) || |
107 | goto_target == VCAP_IS2_CHAIN(0, 0) || |
108 | goto_target == VCAP_IS2_CHAIN(1, 0) || |
109 | goto_target == OCELOT_PSFP_CHAIN); |
110 | |
111 | if (chain == VCAP_IS1_CHAIN(0)) |
112 | return (goto_target == VCAP_IS1_CHAIN(1)); |
113 | |
114 | if (chain == VCAP_IS1_CHAIN(1)) |
115 | return (goto_target == VCAP_IS1_CHAIN(2)); |
116 | |
117 | /* Lookup 2 of VCAP IS1 can really support non-optional GOTOs, |
118 | * using a Policy Association Group (PAG) value, which is an 8-bit |
119 | * value encoding a VCAP IS2 target chain. |
120 | */ |
121 | if (chain == VCAP_IS1_CHAIN(2)) { |
122 | for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) |
123 | if (goto_target == VCAP_IS2_CHAIN(0, pag)) |
124 | return true; |
125 | |
126 | return false; |
127 | } |
128 | |
129 | /* Non-optional GOTO from VCAP IS2 lookup 0 to lookup 1. |
130 | * We cannot change the PAG at this point. |
131 | */ |
132 | for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) |
133 | if (chain == VCAP_IS2_CHAIN(0, pag)) |
134 | return (goto_target == VCAP_IS2_CHAIN(1, pag)); |
135 | |
136 | /* VCAP IS2 lookup 1 can goto to PSFP block if hardware support */ |
137 | for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) |
138 | if (chain == VCAP_IS2_CHAIN(1, pag)) |
139 | return (goto_target == OCELOT_PSFP_CHAIN); |
140 | |
141 | return false; |
142 | } |
143 | |
144 | static struct ocelot_vcap_filter * |
145 | ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain) |
146 | { |
147 | struct ocelot_vcap_filter *filter; |
148 | struct ocelot_vcap_block *block; |
149 | int block_id; |
150 | |
151 | block_id = ocelot_chain_to_block(chain, ingress: true); |
152 | if (block_id < 0) |
153 | return NULL; |
154 | |
155 | if (block_id == VCAP_IS2) { |
156 | block = &ocelot->block[VCAP_IS1]; |
157 | |
158 | list_for_each_entry(filter, &block->rules, list) |
159 | if (filter->type == OCELOT_VCAP_FILTER_PAG && |
160 | filter->goto_target == chain) |
161 | return filter; |
162 | } |
163 | |
164 | list_for_each_entry(filter, &ocelot->dummy_rules, list) |
165 | if (filter->goto_target == chain) |
166 | return filter; |
167 | |
168 | return NULL; |
169 | } |
170 | |
171 | static int |
172 | ocelot_flower_parse_ingress_vlan_modify(struct ocelot *ocelot, int port, |
173 | struct ocelot_vcap_filter *filter, |
174 | const struct flow_action_entry *a, |
175 | struct netlink_ext_ack *extack) |
176 | { |
177 | struct ocelot_port *ocelot_port = ocelot->ports[port]; |
178 | |
179 | if (filter->goto_target != -1) { |
180 | NL_SET_ERR_MSG_MOD(extack, |
181 | "Last action must be GOTO"); |
182 | return -EOPNOTSUPP; |
183 | } |
184 | |
185 | if (!ocelot_port->vlan_aware) { |
186 | NL_SET_ERR_MSG_MOD(extack, |
187 | "Can only modify VLAN under VLAN aware bridge"); |
188 | return -EOPNOTSUPP; |
189 | } |
190 | |
191 | filter->action.vid_replace_ena = true; |
192 | filter->action.pcp_dei_ena = true; |
193 | filter->action.vid = a->vlan.vid; |
194 | filter->action.pcp = a->vlan.prio; |
195 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
196 | |
197 | return 0; |
198 | } |
199 | |
200 | static int |
201 | ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter, |
202 | const struct flow_action_entry *a, |
203 | struct netlink_ext_ack *extack) |
204 | { |
205 | enum ocelot_tag_tpid_sel tpid; |
206 | |
207 | switch (ntohs(a->vlan.proto)) { |
208 | case ETH_P_8021Q: |
209 | tpid = OCELOT_TAG_TPID_SEL_8021Q; |
210 | break; |
211 | case ETH_P_8021AD: |
212 | tpid = OCELOT_TAG_TPID_SEL_8021AD; |
213 | break; |
214 | default: |
215 | NL_SET_ERR_MSG_MOD(extack, |
216 | "Cannot modify custom TPID"); |
217 | return -EOPNOTSUPP; |
218 | } |
219 | |
220 | filter->action.tag_a_tpid_sel = tpid; |
221 | filter->action.push_outer_tag = OCELOT_ES0_TAG; |
222 | filter->action.tag_a_vid_sel = OCELOT_ES0_VID_PLUS_CLASSIFIED_VID; |
223 | filter->action.vid_a_val = a->vlan.vid; |
224 | filter->action.pcp_a_val = a->vlan.prio; |
225 | filter->action.tag_a_pcp_sel = OCELOT_ES0_PCP; |
226 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | static int |
232 | ocelot_flower_parse_egress_port(struct ocelot *ocelot, struct flow_cls_offload *f, |
233 | const struct flow_action_entry *a, bool mirror, |
234 | struct netlink_ext_ack *extack) |
235 | { |
236 | const char *act_string = mirror ? "mirror": "redirect"; |
237 | int egress_port = ocelot->ops->netdev_to_port(a->dev); |
238 | enum flow_action_id offloadable_act_id; |
239 | |
240 | offloadable_act_id = mirror ? FLOW_ACTION_MIRRED : FLOW_ACTION_REDIRECT; |
241 | |
242 | /* Mirroring towards foreign interfaces is handled in software */ |
243 | if (egress_port < 0 || a->id != offloadable_act_id) { |
244 | if (f->common.skip_sw) { |
245 | NL_SET_ERR_MSG_FMT(extack, |
246 | "Can only %s to %s if filter also runs in software", |
247 | act_string, egress_port < 0 ? |
248 | "CPU": "ingress of ocelot port"); |
249 | return -EOPNOTSUPP; |
250 | } |
251 | egress_port = ocelot->num_phys_ports; |
252 | } |
253 | |
254 | return egress_port; |
255 | } |
256 | |
257 | static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, |
258 | bool ingress, struct flow_cls_offload *f, |
259 | struct ocelot_vcap_filter *filter) |
260 | { |
261 | const struct flow_action *action = &f->rule->action; |
262 | struct netlink_ext_ack *extack = f->common.extack; |
263 | bool allow_missing_goto_target = false; |
264 | const struct flow_action_entry *a; |
265 | enum ocelot_tag_tpid_sel tpid; |
266 | int i, chain, egress_port; |
267 | u32 pol_ix, pol_max; |
268 | u64 rate; |
269 | int err; |
270 | |
271 | if (!flow_action_basic_hw_stats_check(action: &f->rule->action, |
272 | extack: f->common.extack)) |
273 | return -EOPNOTSUPP; |
274 | |
275 | chain = f->common.chain_index; |
276 | filter->block_id = ocelot_chain_to_block(chain, ingress); |
277 | if (filter->block_id < 0) { |
278 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain"); |
279 | return -EOPNOTSUPP; |
280 | } |
281 | if (filter->block_id == VCAP_IS1 || filter->block_id == VCAP_IS2) |
282 | filter->lookup = ocelot_chain_to_lookup(chain); |
283 | if (filter->block_id == VCAP_IS2) |
284 | filter->pag = ocelot_chain_to_pag(chain); |
285 | |
286 | filter->goto_target = -1; |
287 | filter->type = OCELOT_VCAP_FILTER_DUMMY; |
288 | |
289 | flow_action_for_each(i, a, action) { |
290 | switch (a->id) { |
291 | case FLOW_ACTION_DROP: |
292 | if (filter->block_id != VCAP_IS2) { |
293 | NL_SET_ERR_MSG_MOD(extack, |
294 | "Drop action can only be offloaded to VCAP IS2"); |
295 | return -EOPNOTSUPP; |
296 | } |
297 | if (filter->goto_target != -1) { |
298 | NL_SET_ERR_MSG_MOD(extack, |
299 | "Last action must be GOTO"); |
300 | return -EOPNOTSUPP; |
301 | } |
302 | filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; |
303 | filter->action.port_mask = 0; |
304 | filter->action.police_ena = true; |
305 | filter->action.pol_ix = OCELOT_POLICER_DISCARD; |
306 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
307 | break; |
308 | case FLOW_ACTION_ACCEPT: |
309 | if (filter->block_id != VCAP_ES0 && |
310 | filter->block_id != VCAP_IS1 && |
311 | filter->block_id != VCAP_IS2) { |
312 | NL_SET_ERR_MSG_MOD(extack, |
313 | "Accept action can only be offloaded to VCAP chains"); |
314 | return -EOPNOTSUPP; |
315 | } |
316 | if (filter->block_id != VCAP_ES0 && |
317 | filter->goto_target != -1) { |
318 | NL_SET_ERR_MSG_MOD(extack, |
319 | "Last action must be GOTO"); |
320 | return -EOPNOTSUPP; |
321 | } |
322 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
323 | break; |
324 | case FLOW_ACTION_TRAP: |
325 | if (filter->block_id != VCAP_IS2 || |
326 | filter->lookup != 0) { |
327 | NL_SET_ERR_MSG_MOD(extack, |
328 | "Trap action can only be offloaded to VCAP IS2 lookup 0"); |
329 | return -EOPNOTSUPP; |
330 | } |
331 | if (filter->goto_target != -1) { |
332 | NL_SET_ERR_MSG_MOD(extack, |
333 | "Last action must be GOTO"); |
334 | return -EOPNOTSUPP; |
335 | } |
336 | filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; |
337 | filter->action.port_mask = 0; |
338 | filter->action.cpu_copy_ena = true; |
339 | filter->action.cpu_qu_num = 0; |
340 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
341 | filter->is_trap = true; |
342 | break; |
343 | case FLOW_ACTION_POLICE: |
344 | if (filter->block_id == PSFP_BLOCK_ID) { |
345 | filter->type = OCELOT_PSFP_FILTER_OFFLOAD; |
346 | break; |
347 | } |
348 | if (filter->block_id != VCAP_IS2 || |
349 | filter->lookup != 0) { |
350 | NL_SET_ERR_MSG_MOD(extack, |
351 | "Police action can only be offloaded to VCAP IS2 lookup 0 or PSFP"); |
352 | return -EOPNOTSUPP; |
353 | } |
354 | if (filter->goto_target != -1) { |
355 | NL_SET_ERR_MSG_MOD(extack, |
356 | "Last action must be GOTO"); |
357 | return -EOPNOTSUPP; |
358 | } |
359 | |
360 | err = ocelot_policer_validate(action, a, extack); |
361 | if (err) |
362 | return err; |
363 | |
364 | filter->action.police_ena = true; |
365 | |
366 | pol_ix = a->hw_index + ocelot->vcap_pol.base; |
367 | pol_max = ocelot->vcap_pol.max; |
368 | |
369 | if (ocelot->vcap_pol.max2 && pol_ix > pol_max) { |
370 | pol_ix += ocelot->vcap_pol.base2 - pol_max - 1; |
371 | pol_max = ocelot->vcap_pol.max2; |
372 | } |
373 | |
374 | if (pol_ix >= pol_max) |
375 | return -EINVAL; |
376 | |
377 | filter->action.pol_ix = pol_ix; |
378 | |
379 | rate = a->police.rate_bytes_ps; |
380 | filter->action.pol.rate = div_u64(dividend: rate, divisor: 1000) * 8; |
381 | filter->action.pol.burst = a->police.burst; |
382 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
383 | break; |
384 | case FLOW_ACTION_REDIRECT: |
385 | case FLOW_ACTION_REDIRECT_INGRESS: |
386 | if (filter->block_id != VCAP_IS2) { |
387 | NL_SET_ERR_MSG_MOD(extack, |
388 | "Redirect action can only be offloaded to VCAP IS2"); |
389 | return -EOPNOTSUPP; |
390 | } |
391 | if (filter->goto_target != -1) { |
392 | NL_SET_ERR_MSG_MOD(extack, |
393 | "Last action must be GOTO"); |
394 | return -EOPNOTSUPP; |
395 | } |
396 | |
397 | egress_port = ocelot_flower_parse_egress_port(ocelot, f, |
398 | a, mirror: false, |
399 | extack); |
400 | if (egress_port < 0) |
401 | return egress_port; |
402 | |
403 | filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT; |
404 | filter->action.port_mask = BIT(egress_port); |
405 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
406 | break; |
407 | case FLOW_ACTION_MIRRED: |
408 | case FLOW_ACTION_MIRRED_INGRESS: |
409 | if (filter->block_id != VCAP_IS2) { |
410 | NL_SET_ERR_MSG_MOD(extack, |
411 | "Mirror action can only be offloaded to VCAP IS2"); |
412 | return -EOPNOTSUPP; |
413 | } |
414 | if (filter->goto_target != -1) { |
415 | NL_SET_ERR_MSG_MOD(extack, |
416 | "Last action must be GOTO"); |
417 | return -EOPNOTSUPP; |
418 | } |
419 | |
420 | egress_port = ocelot_flower_parse_egress_port(ocelot, f, |
421 | a, mirror: true, |
422 | extack); |
423 | if (egress_port < 0) |
424 | return egress_port; |
425 | |
426 | filter->egress_port.value = egress_port; |
427 | filter->action.mirror_ena = true; |
428 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
429 | break; |
430 | case FLOW_ACTION_VLAN_POP: |
431 | if (filter->block_id != VCAP_IS1) { |
432 | NL_SET_ERR_MSG_MOD(extack, |
433 | "VLAN pop action can only be offloaded to VCAP IS1"); |
434 | return -EOPNOTSUPP; |
435 | } |
436 | if (filter->goto_target != -1) { |
437 | NL_SET_ERR_MSG_MOD(extack, |
438 | "Last action must be GOTO"); |
439 | return -EOPNOTSUPP; |
440 | } |
441 | filter->action.vlan_pop_cnt_ena = true; |
442 | filter->action.vlan_pop_cnt++; |
443 | if (filter->action.vlan_pop_cnt > 2) { |
444 | NL_SET_ERR_MSG_MOD(extack, |
445 | "Cannot pop more than 2 VLAN headers"); |
446 | return -EOPNOTSUPP; |
447 | } |
448 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
449 | break; |
450 | case FLOW_ACTION_VLAN_MANGLE: |
451 | if (filter->block_id == VCAP_IS1) { |
452 | err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port, |
453 | filter, a, |
454 | extack); |
455 | } else if (filter->block_id == VCAP_ES0) { |
456 | err = ocelot_flower_parse_egress_vlan_modify(filter, a, |
457 | extack); |
458 | } else { |
459 | NL_SET_ERR_MSG_MOD(extack, |
460 | "VLAN modify action can only be offloaded to VCAP IS1 or ES0"); |
461 | err = -EOPNOTSUPP; |
462 | } |
463 | if (err) |
464 | return err; |
465 | break; |
466 | case FLOW_ACTION_PRIORITY: |
467 | if (filter->block_id != VCAP_IS1) { |
468 | NL_SET_ERR_MSG_MOD(extack, |
469 | "Priority action can only be offloaded to VCAP IS1"); |
470 | return -EOPNOTSUPP; |
471 | } |
472 | if (filter->goto_target != -1) { |
473 | NL_SET_ERR_MSG_MOD(extack, |
474 | "Last action must be GOTO"); |
475 | return -EOPNOTSUPP; |
476 | } |
477 | filter->action.qos_ena = true; |
478 | filter->action.qos_val = a->priority; |
479 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
480 | break; |
481 | case FLOW_ACTION_GOTO: |
482 | filter->goto_target = a->chain_index; |
483 | |
484 | if (filter->block_id == VCAP_IS1 && filter->lookup == 2) { |
485 | int pag = ocelot_chain_to_pag(chain: filter->goto_target); |
486 | |
487 | filter->action.pag_override_mask = 0xff; |
488 | filter->action.pag_val = pag; |
489 | filter->type = OCELOT_VCAP_FILTER_PAG; |
490 | } |
491 | break; |
492 | case FLOW_ACTION_VLAN_PUSH: |
493 | if (filter->block_id != VCAP_ES0) { |
494 | NL_SET_ERR_MSG_MOD(extack, |
495 | "VLAN push action can only be offloaded to VCAP ES0"); |
496 | return -EOPNOTSUPP; |
497 | } |
498 | switch (ntohs(a->vlan.proto)) { |
499 | case ETH_P_8021Q: |
500 | tpid = OCELOT_TAG_TPID_SEL_8021Q; |
501 | break; |
502 | case ETH_P_8021AD: |
503 | tpid = OCELOT_TAG_TPID_SEL_8021AD; |
504 | break; |
505 | default: |
506 | NL_SET_ERR_MSG_MOD(extack, |
507 | "Cannot push custom TPID"); |
508 | return -EOPNOTSUPP; |
509 | } |
510 | filter->action.tag_a_tpid_sel = tpid; |
511 | filter->action.push_outer_tag = OCELOT_ES0_TAG; |
512 | filter->action.tag_a_vid_sel = OCELOT_ES0_VID; |
513 | filter->action.vid_a_val = a->vlan.vid; |
514 | filter->action.pcp_a_val = a->vlan.prio; |
515 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
516 | break; |
517 | case FLOW_ACTION_GATE: |
518 | if (filter->block_id != PSFP_BLOCK_ID) { |
519 | NL_SET_ERR_MSG_MOD(extack, |
520 | "Gate action can only be offloaded to PSFP chain"); |
521 | return -EOPNOTSUPP; |
522 | } |
523 | filter->type = OCELOT_PSFP_FILTER_OFFLOAD; |
524 | break; |
525 | default: |
526 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload action"); |
527 | return -EOPNOTSUPP; |
528 | } |
529 | } |
530 | |
531 | if (filter->goto_target == -1) { |
532 | if ((filter->block_id == VCAP_IS2 && filter->lookup == 1) || |
533 | chain == 0 || filter->block_id == PSFP_BLOCK_ID) { |
534 | allow_missing_goto_target = true; |
535 | } else { |
536 | NL_SET_ERR_MSG_MOD(extack, "Missing GOTO action"); |
537 | return -EOPNOTSUPP; |
538 | } |
539 | } |
540 | |
541 | if (!ocelot_is_goto_target_valid(goto_target: filter->goto_target, chain, ingress) && |
542 | !allow_missing_goto_target) { |
543 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload this GOTO target"); |
544 | return -EOPNOTSUPP; |
545 | } |
546 | |
547 | return 0; |
548 | } |
549 | |
550 | static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port, |
551 | struct flow_cls_offload *f, |
552 | struct ocelot_vcap_filter *filter) |
553 | { |
554 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: f); |
555 | const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; |
556 | int key_length = vcap->keys[VCAP_ES0_IGR_PORT].length; |
557 | struct netlink_ext_ack *extack = f->common.extack; |
558 | struct net_device *dev, *indev; |
559 | struct flow_match_meta match; |
560 | int ingress_port; |
561 | |
562 | flow_rule_match_meta(rule, out: &match); |
563 | |
564 | if (!match.mask->ingress_ifindex) |
565 | return 0; |
566 | |
567 | if (match.mask->ingress_ifindex != 0xFFFFFFFF) { |
568 | NL_SET_ERR_MSG_MOD(extack, "Unsupported ingress ifindex mask"); |
569 | return -EOPNOTSUPP; |
570 | } |
571 | |
572 | dev = ocelot->ops->port_to_netdev(ocelot, port); |
573 | if (!dev) |
574 | return -EINVAL; |
575 | |
576 | indev = __dev_get_by_index(net: dev_net(dev), ifindex: match.key->ingress_ifindex); |
577 | if (!indev) { |
578 | NL_SET_ERR_MSG_MOD(extack, |
579 | "Can't find the ingress port to match on"); |
580 | return -ENOENT; |
581 | } |
582 | |
583 | ingress_port = ocelot->ops->netdev_to_port(indev); |
584 | if (ingress_port < 0) { |
585 | NL_SET_ERR_MSG_MOD(extack, |
586 | "Can only offload an ocelot ingress port"); |
587 | return -EOPNOTSUPP; |
588 | } |
589 | if (ingress_port == port) { |
590 | NL_SET_ERR_MSG_MOD(extack, |
591 | "Ingress port is equal to the egress port"); |
592 | return -EINVAL; |
593 | } |
594 | |
595 | filter->ingress_port.value = ingress_port; |
596 | filter->ingress_port.mask = GENMASK(key_length - 1, 0); |
597 | |
598 | return 0; |
599 | } |
600 | |
601 | static int |
602 | ocelot_flower_parse_key(struct ocelot *ocelot, int port, bool ingress, |
603 | struct flow_cls_offload *f, |
604 | struct ocelot_vcap_filter *filter) |
605 | { |
606 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: f); |
607 | struct flow_dissector *dissector = rule->match.dissector; |
608 | struct netlink_ext_ack *extack = f->common.extack; |
609 | u16 proto = ntohs(f->common.protocol); |
610 | bool match_protocol = true; |
611 | int ret; |
612 | |
613 | if (dissector->used_keys & |
614 | ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | |
615 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
616 | BIT_ULL(FLOW_DISSECTOR_KEY_META) | |
617 | BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) | |
618 | BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) | |
619 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | |
620 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | |
621 | BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { |
622 | return -EOPNOTSUPP; |
623 | } |
624 | |
625 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_META)) { |
626 | struct flow_match_meta match; |
627 | |
628 | flow_rule_match_meta(rule, out: &match); |
629 | if (match.mask->l2_miss) { |
630 | NL_SET_ERR_MSG_MOD(extack, "Can't match on \"l2_miss\""); |
631 | return -EOPNOTSUPP; |
632 | } |
633 | } |
634 | |
635 | /* For VCAP ES0 (egress rewriter) we can match on the ingress port */ |
636 | if (!ingress) { |
637 | ret = ocelot_flower_parse_indev(ocelot, port, f, filter); |
638 | if (ret) |
639 | return ret; |
640 | } |
641 | |
642 | if (flow_rule_match_has_control_flags(rule, extack)) |
643 | return -EOPNOTSUPP; |
644 | |
645 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_VLAN)) { |
646 | struct flow_match_vlan match; |
647 | |
648 | flow_rule_match_vlan(rule, out: &match); |
649 | filter->key_type = OCELOT_VCAP_KEY_ANY; |
650 | filter->vlan.vid.value = match.key->vlan_id; |
651 | filter->vlan.vid.mask = match.mask->vlan_id; |
652 | filter->vlan.pcp.value[0] = match.key->vlan_priority; |
653 | filter->vlan.pcp.mask[0] = match.mask->vlan_priority; |
654 | match_protocol = false; |
655 | } |
656 | |
657 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
658 | struct flow_match_eth_addrs match; |
659 | |
660 | if (filter->block_id == VCAP_ES0) { |
661 | NL_SET_ERR_MSG_MOD(extack, |
662 | "VCAP ES0 cannot match on MAC address"); |
663 | return -EOPNOTSUPP; |
664 | } |
665 | |
666 | /* The hw support mac matches only for MAC_ETYPE key, |
667 | * therefore if other matches(port, tcp flags, etc) are added |
668 | * then just bail out |
669 | */ |
670 | if ((dissector->used_keys & |
671 | (BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
672 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
673 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) != |
674 | (BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
675 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
676 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) |
677 | return -EOPNOTSUPP; |
678 | |
679 | flow_rule_match_eth_addrs(rule, out: &match); |
680 | |
681 | if (filter->block_id == VCAP_IS1 && |
682 | !is_zero_ether_addr(addr: match.mask->dst)) { |
683 | NL_SET_ERR_MSG_MOD(extack, |
684 | "Key type S1_NORMAL cannot match on destination MAC"); |
685 | return -EOPNOTSUPP; |
686 | } |
687 | |
688 | filter->key_type = OCELOT_VCAP_KEY_ETYPE; |
689 | ether_addr_copy(dst: filter->key.etype.dmac.value, |
690 | src: match.key->dst); |
691 | ether_addr_copy(dst: filter->key.etype.smac.value, |
692 | src: match.key->src); |
693 | ether_addr_copy(dst: filter->key.etype.dmac.mask, |
694 | src: match.mask->dst); |
695 | ether_addr_copy(dst: filter->key.etype.smac.mask, |
696 | src: match.mask->src); |
697 | goto finished_key_parsing; |
698 | } |
699 | |
700 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_BASIC)) { |
701 | struct flow_match_basic match; |
702 | |
703 | flow_rule_match_basic(rule, out: &match); |
704 | if (ntohs(match.key->n_proto) == ETH_P_IP) { |
705 | if (filter->block_id == VCAP_ES0) { |
706 | NL_SET_ERR_MSG_MOD(extack, |
707 | "VCAP ES0 cannot match on IP protocol"); |
708 | return -EOPNOTSUPP; |
709 | } |
710 | |
711 | filter->key_type = OCELOT_VCAP_KEY_IPV4; |
712 | filter->key.ipv4.proto.value[0] = |
713 | match.key->ip_proto; |
714 | filter->key.ipv4.proto.mask[0] = |
715 | match.mask->ip_proto; |
716 | match_protocol = false; |
717 | } |
718 | if (ntohs(match.key->n_proto) == ETH_P_IPV6) { |
719 | if (filter->block_id == VCAP_ES0) { |
720 | NL_SET_ERR_MSG_MOD(extack, |
721 | "VCAP ES0 cannot match on IP protocol"); |
722 | return -EOPNOTSUPP; |
723 | } |
724 | |
725 | filter->key_type = OCELOT_VCAP_KEY_IPV6; |
726 | filter->key.ipv6.proto.value[0] = |
727 | match.key->ip_proto; |
728 | filter->key.ipv6.proto.mask[0] = |
729 | match.mask->ip_proto; |
730 | match_protocol = false; |
731 | } |
732 | } |
733 | |
734 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IPV4_ADDRS) && |
735 | proto == ETH_P_IP) { |
736 | struct flow_match_ipv4_addrs match; |
737 | u8 *tmp; |
738 | |
739 | if (filter->block_id == VCAP_ES0) { |
740 | NL_SET_ERR_MSG_MOD(extack, |
741 | "VCAP ES0 cannot match on IP address"); |
742 | return -EOPNOTSUPP; |
743 | } |
744 | |
745 | flow_rule_match_ipv4_addrs(rule, out: &match); |
746 | |
747 | if (filter->block_id == VCAP_IS1 && *(u32 *)&match.mask->dst) { |
748 | NL_SET_ERR_MSG_MOD(extack, |
749 | "Key type S1_NORMAL cannot match on destination IP"); |
750 | return -EOPNOTSUPP; |
751 | } |
752 | |
753 | tmp = &filter->key.ipv4.sip.value.addr[0]; |
754 | memcpy(tmp, &match.key->src, 4); |
755 | |
756 | tmp = &filter->key.ipv4.sip.mask.addr[0]; |
757 | memcpy(tmp, &match.mask->src, 4); |
758 | |
759 | tmp = &filter->key.ipv4.dip.value.addr[0]; |
760 | memcpy(tmp, &match.key->dst, 4); |
761 | |
762 | tmp = &filter->key.ipv4.dip.mask.addr[0]; |
763 | memcpy(tmp, &match.mask->dst, 4); |
764 | match_protocol = false; |
765 | } |
766 | |
767 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IPV6_ADDRS) && |
768 | proto == ETH_P_IPV6) { |
769 | return -EOPNOTSUPP; |
770 | } |
771 | |
772 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_PORTS)) { |
773 | struct flow_match_ports match; |
774 | |
775 | if (filter->block_id == VCAP_ES0) { |
776 | NL_SET_ERR_MSG_MOD(extack, |
777 | "VCAP ES0 cannot match on L4 ports"); |
778 | return -EOPNOTSUPP; |
779 | } |
780 | |
781 | flow_rule_match_ports(rule, out: &match); |
782 | filter->key.ipv4.sport.value = ntohs(match.key->src); |
783 | filter->key.ipv4.sport.mask = ntohs(match.mask->src); |
784 | filter->key.ipv4.dport.value = ntohs(match.key->dst); |
785 | filter->key.ipv4.dport.mask = ntohs(match.mask->dst); |
786 | match_protocol = false; |
787 | } |
788 | |
789 | finished_key_parsing: |
790 | if (match_protocol && proto != ETH_P_ALL) { |
791 | if (filter->block_id == VCAP_ES0) { |
792 | NL_SET_ERR_MSG_MOD(extack, |
793 | "VCAP ES0 cannot match on L2 proto"); |
794 | return -EOPNOTSUPP; |
795 | } |
796 | |
797 | /* TODO: support SNAP, LLC etc */ |
798 | if (proto < ETH_P_802_3_MIN) |
799 | return -EOPNOTSUPP; |
800 | filter->key_type = OCELOT_VCAP_KEY_ETYPE; |
801 | *(__be16 *)filter->key.etype.etype.value = htons(proto); |
802 | *(__be16 *)filter->key.etype.etype.mask = htons(0xffff); |
803 | } |
804 | /* else, a filter of type OCELOT_VCAP_KEY_ANY is implicitly added */ |
805 | |
806 | return 0; |
807 | } |
808 | |
809 | static int ocelot_flower_parse(struct ocelot *ocelot, int port, bool ingress, |
810 | struct flow_cls_offload *f, |
811 | struct ocelot_vcap_filter *filter) |
812 | { |
813 | int ret; |
814 | |
815 | filter->prio = f->common.prio; |
816 | filter->id.cookie = f->cookie; |
817 | filter->id.tc_offload = true; |
818 | |
819 | ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter); |
820 | if (ret) |
821 | return ret; |
822 | |
823 | /* PSFP filter need to parse key by stream identification function. */ |
824 | if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) |
825 | return 0; |
826 | |
827 | return ocelot_flower_parse_key(ocelot, port, ingress, f, filter); |
828 | } |
829 | |
830 | static struct ocelot_vcap_filter |
831 | *ocelot_vcap_filter_create(struct ocelot *ocelot, int port, bool ingress, |
832 | struct flow_cls_offload *f) |
833 | { |
834 | struct ocelot_vcap_filter *filter; |
835 | |
836 | filter = kzalloc(sizeof(*filter), GFP_KERNEL); |
837 | if (!filter) |
838 | return NULL; |
839 | |
840 | if (ingress) { |
841 | filter->ingress_port_mask = BIT(port); |
842 | } else { |
843 | const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; |
844 | int key_length = vcap->keys[VCAP_ES0_EGR_PORT].length; |
845 | |
846 | filter->egress_port.value = port; |
847 | filter->egress_port.mask = GENMASK(key_length - 1, 0); |
848 | } |
849 | |
850 | return filter; |
851 | } |
852 | |
853 | static int ocelot_vcap_dummy_filter_add(struct ocelot *ocelot, |
854 | struct ocelot_vcap_filter *filter) |
855 | { |
856 | list_add(new: &filter->list, head: &ocelot->dummy_rules); |
857 | |
858 | return 0; |
859 | } |
860 | |
861 | static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot, |
862 | struct ocelot_vcap_filter *filter) |
863 | { |
864 | list_del(entry: &filter->list); |
865 | kfree(objp: filter); |
866 | |
867 | return 0; |
868 | } |
869 | |
870 | /* If we have an egress VLAN modification rule, we need to actually write the |
871 | * delta between the input VLAN (from the key) and the output VLAN (from the |
872 | * action), but the action was parsed first. So we need to patch the delta into |
873 | * the action here. |
874 | */ |
875 | static int |
876 | ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter, |
877 | struct netlink_ext_ack *extack) |
878 | { |
879 | if (filter->block_id != VCAP_ES0 || |
880 | filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID) |
881 | return 0; |
882 | |
883 | if (filter->vlan.vid.mask != VLAN_VID_MASK) { |
884 | NL_SET_ERR_MSG_MOD(extack, |
885 | "VCAP ES0 VLAN rewriting needs a full VLAN in the key"); |
886 | return -EOPNOTSUPP; |
887 | } |
888 | |
889 | filter->action.vid_a_val -= filter->vlan.vid.value; |
890 | filter->action.vid_a_val &= VLAN_VID_MASK; |
891 | |
892 | return 0; |
893 | } |
894 | |
895 | int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, |
896 | struct flow_cls_offload *f, bool ingress) |
897 | { |
898 | struct netlink_ext_ack *extack = f->common.extack; |
899 | struct ocelot_vcap_filter *filter; |
900 | int chain = f->common.chain_index; |
901 | int block_id, ret; |
902 | |
903 | if (chain && !ocelot_find_vcap_filter_that_points_at(ocelot, chain)) { |
904 | NL_SET_ERR_MSG_MOD(extack, "No default GOTO action points to this chain"); |
905 | return -EOPNOTSUPP; |
906 | } |
907 | |
908 | block_id = ocelot_chain_to_block(chain, ingress); |
909 | if (block_id < 0) { |
910 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain"); |
911 | return -EOPNOTSUPP; |
912 | } |
913 | |
914 | filter = ocelot_vcap_block_find_filter_by_id(block: &ocelot->block[block_id], |
915 | cookie: f->cookie, tc_offload: true); |
916 | if (filter) { |
917 | /* Filter already exists on other ports */ |
918 | if (!ingress) { |
919 | NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 does not support shared filters"); |
920 | return -EOPNOTSUPP; |
921 | } |
922 | |
923 | filter->ingress_port_mask |= BIT(port); |
924 | |
925 | return ocelot_vcap_filter_replace(ocelot, filter); |
926 | } |
927 | |
928 | /* Filter didn't exist, create it now */ |
929 | filter = ocelot_vcap_filter_create(ocelot, port, ingress, f); |
930 | if (!filter) |
931 | return -ENOMEM; |
932 | |
933 | ret = ocelot_flower_parse(ocelot, port, ingress, f, filter); |
934 | if (ret) { |
935 | kfree(objp: filter); |
936 | return ret; |
937 | } |
938 | |
939 | ret = ocelot_flower_patch_es0_vlan_modify(filter, extack); |
940 | if (ret) { |
941 | kfree(objp: filter); |
942 | return ret; |
943 | } |
944 | |
945 | /* The non-optional GOTOs for the TCAM skeleton don't need |
946 | * to be actually offloaded. |
947 | */ |
948 | if (filter->type == OCELOT_VCAP_FILTER_DUMMY) |
949 | return ocelot_vcap_dummy_filter_add(ocelot, filter); |
950 | |
951 | if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) { |
952 | kfree(objp: filter); |
953 | if (ocelot->ops->psfp_filter_add) |
954 | return ocelot->ops->psfp_filter_add(ocelot, port, f); |
955 | |
956 | NL_SET_ERR_MSG_MOD(extack, "PSFP chain is not supported in HW"); |
957 | return -EOPNOTSUPP; |
958 | } |
959 | |
960 | return ocelot_vcap_filter_add(ocelot, rule: filter, extack: f->common.extack); |
961 | } |
962 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_replace); |
963 | |
964 | int ocelot_cls_flower_destroy(struct ocelot *ocelot, int port, |
965 | struct flow_cls_offload *f, bool ingress) |
966 | { |
967 | struct ocelot_vcap_filter *filter; |
968 | struct ocelot_vcap_block *block; |
969 | int block_id; |
970 | |
971 | block_id = ocelot_chain_to_block(chain: f->common.chain_index, ingress); |
972 | if (block_id < 0) |
973 | return 0; |
974 | |
975 | if (block_id == PSFP_BLOCK_ID) { |
976 | if (ocelot->ops->psfp_filter_del) |
977 | return ocelot->ops->psfp_filter_del(ocelot, f); |
978 | |
979 | return -EOPNOTSUPP; |
980 | } |
981 | |
982 | block = &ocelot->block[block_id]; |
983 | |
984 | filter = ocelot_vcap_block_find_filter_by_id(block, cookie: f->cookie, tc_offload: true); |
985 | if (!filter) |
986 | return 0; |
987 | |
988 | if (filter->type == OCELOT_VCAP_FILTER_DUMMY) |
989 | return ocelot_vcap_dummy_filter_del(ocelot, filter); |
990 | |
991 | if (ingress) { |
992 | filter->ingress_port_mask &= ~BIT(port); |
993 | if (filter->ingress_port_mask) |
994 | return ocelot_vcap_filter_replace(ocelot, filter); |
995 | } |
996 | |
997 | return ocelot_vcap_filter_del(ocelot, rule: filter); |
998 | } |
999 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_destroy); |
1000 | |
1001 | int ocelot_cls_flower_stats(struct ocelot *ocelot, int port, |
1002 | struct flow_cls_offload *f, bool ingress) |
1003 | { |
1004 | struct ocelot_vcap_filter *filter; |
1005 | struct ocelot_vcap_block *block; |
1006 | struct flow_stats stats = {0}; |
1007 | int block_id, ret; |
1008 | |
1009 | block_id = ocelot_chain_to_block(chain: f->common.chain_index, ingress); |
1010 | if (block_id < 0) |
1011 | return 0; |
1012 | |
1013 | if (block_id == PSFP_BLOCK_ID) { |
1014 | if (ocelot->ops->psfp_stats_get) { |
1015 | ret = ocelot->ops->psfp_stats_get(ocelot, f, &stats); |
1016 | if (ret) |
1017 | return ret; |
1018 | |
1019 | goto stats_update; |
1020 | } |
1021 | |
1022 | return -EOPNOTSUPP; |
1023 | } |
1024 | |
1025 | block = &ocelot->block[block_id]; |
1026 | |
1027 | filter = ocelot_vcap_block_find_filter_by_id(block, cookie: f->cookie, tc_offload: true); |
1028 | if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY) |
1029 | return 0; |
1030 | |
1031 | ret = ocelot_vcap_filter_stats_update(ocelot, rule: filter); |
1032 | if (ret) |
1033 | return ret; |
1034 | |
1035 | stats.pkts = filter->stats.pkts; |
1036 | |
1037 | stats_update: |
1038 | flow_stats_update(flow_stats: &f->stats, bytes: 0x0, pkts: stats.pkts, drops: stats.drops, lastused: 0x0, |
1039 | used_hw_stats: FLOW_ACTION_HW_STATS_IMMEDIATE); |
1040 | return 0; |
1041 | } |
1042 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats); |
1043 |
Definitions
- ocelot_chain_to_block
- ocelot_chain_to_lookup
- ocelot_chain_to_pag
- ocelot_is_goto_target_valid
- ocelot_find_vcap_filter_that_points_at
- ocelot_flower_parse_ingress_vlan_modify
- ocelot_flower_parse_egress_vlan_modify
- ocelot_flower_parse_egress_port
- ocelot_flower_parse_action
- ocelot_flower_parse_indev
- ocelot_flower_parse_key
- ocelot_flower_parse
- ocelot_vcap_filter_create
- ocelot_vcap_dummy_filter_add
- ocelot_vcap_dummy_filter_del
- ocelot_flower_patch_es0_vlan_modify
- ocelot_cls_flower_replace
- ocelot_cls_flower_destroy
Improve your Profiling and Debugging skills
Find out more