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 ocelot_flower_parse_action(struct ocelot *ocelot, int port, |
232 | bool ingress, struct flow_cls_offload *f, |
233 | struct ocelot_vcap_filter *filter) |
234 | { |
235 | const struct flow_action *action = &f->rule->action; |
236 | struct netlink_ext_ack *extack = f->common.extack; |
237 | bool allow_missing_goto_target = false; |
238 | const struct flow_action_entry *a; |
239 | enum ocelot_tag_tpid_sel tpid; |
240 | int i, chain, egress_port; |
241 | u32 pol_ix, pol_max; |
242 | u64 rate; |
243 | int err; |
244 | |
245 | if (!flow_action_basic_hw_stats_check(action: &f->rule->action, |
246 | extack: f->common.extack)) |
247 | return -EOPNOTSUPP; |
248 | |
249 | chain = f->common.chain_index; |
250 | filter->block_id = ocelot_chain_to_block(chain, ingress); |
251 | if (filter->block_id < 0) { |
252 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain" ); |
253 | return -EOPNOTSUPP; |
254 | } |
255 | if (filter->block_id == VCAP_IS1 || filter->block_id == VCAP_IS2) |
256 | filter->lookup = ocelot_chain_to_lookup(chain); |
257 | if (filter->block_id == VCAP_IS2) |
258 | filter->pag = ocelot_chain_to_pag(chain); |
259 | |
260 | filter->goto_target = -1; |
261 | filter->type = OCELOT_VCAP_FILTER_DUMMY; |
262 | |
263 | flow_action_for_each(i, a, action) { |
264 | switch (a->id) { |
265 | case FLOW_ACTION_DROP: |
266 | if (filter->block_id != VCAP_IS2) { |
267 | NL_SET_ERR_MSG_MOD(extack, |
268 | "Drop action can only be offloaded to VCAP IS2" ); |
269 | return -EOPNOTSUPP; |
270 | } |
271 | if (filter->goto_target != -1) { |
272 | NL_SET_ERR_MSG_MOD(extack, |
273 | "Last action must be GOTO" ); |
274 | return -EOPNOTSUPP; |
275 | } |
276 | filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; |
277 | filter->action.port_mask = 0; |
278 | filter->action.police_ena = true; |
279 | filter->action.pol_ix = OCELOT_POLICER_DISCARD; |
280 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
281 | break; |
282 | case FLOW_ACTION_ACCEPT: |
283 | if (filter->block_id != VCAP_ES0 && |
284 | filter->block_id != VCAP_IS1 && |
285 | filter->block_id != VCAP_IS2) { |
286 | NL_SET_ERR_MSG_MOD(extack, |
287 | "Accept action can only be offloaded to VCAP chains" ); |
288 | return -EOPNOTSUPP; |
289 | } |
290 | if (filter->block_id != VCAP_ES0 && |
291 | filter->goto_target != -1) { |
292 | NL_SET_ERR_MSG_MOD(extack, |
293 | "Last action must be GOTO" ); |
294 | return -EOPNOTSUPP; |
295 | } |
296 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
297 | break; |
298 | case FLOW_ACTION_TRAP: |
299 | if (filter->block_id != VCAP_IS2 || |
300 | filter->lookup != 0) { |
301 | NL_SET_ERR_MSG_MOD(extack, |
302 | "Trap action can only be offloaded to VCAP IS2 lookup 0" ); |
303 | return -EOPNOTSUPP; |
304 | } |
305 | if (filter->goto_target != -1) { |
306 | NL_SET_ERR_MSG_MOD(extack, |
307 | "Last action must be GOTO" ); |
308 | return -EOPNOTSUPP; |
309 | } |
310 | filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; |
311 | filter->action.port_mask = 0; |
312 | filter->action.cpu_copy_ena = true; |
313 | filter->action.cpu_qu_num = 0; |
314 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
315 | filter->is_trap = true; |
316 | break; |
317 | case FLOW_ACTION_POLICE: |
318 | if (filter->block_id == PSFP_BLOCK_ID) { |
319 | filter->type = OCELOT_PSFP_FILTER_OFFLOAD; |
320 | break; |
321 | } |
322 | if (filter->block_id != VCAP_IS2 || |
323 | filter->lookup != 0) { |
324 | NL_SET_ERR_MSG_MOD(extack, |
325 | "Police action can only be offloaded to VCAP IS2 lookup 0 or PSFP" ); |
326 | return -EOPNOTSUPP; |
327 | } |
328 | if (filter->goto_target != -1) { |
329 | NL_SET_ERR_MSG_MOD(extack, |
330 | "Last action must be GOTO" ); |
331 | return -EOPNOTSUPP; |
332 | } |
333 | |
334 | err = ocelot_policer_validate(action, a, extack); |
335 | if (err) |
336 | return err; |
337 | |
338 | filter->action.police_ena = true; |
339 | |
340 | pol_ix = a->hw_index + ocelot->vcap_pol.base; |
341 | pol_max = ocelot->vcap_pol.max; |
342 | |
343 | if (ocelot->vcap_pol.max2 && pol_ix > pol_max) { |
344 | pol_ix += ocelot->vcap_pol.base2 - pol_max - 1; |
345 | pol_max = ocelot->vcap_pol.max2; |
346 | } |
347 | |
348 | if (pol_ix >= pol_max) |
349 | return -EINVAL; |
350 | |
351 | filter->action.pol_ix = pol_ix; |
352 | |
353 | rate = a->police.rate_bytes_ps; |
354 | filter->action.pol.rate = div_u64(dividend: rate, divisor: 1000) * 8; |
355 | filter->action.pol.burst = a->police.burst; |
356 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
357 | break; |
358 | case FLOW_ACTION_REDIRECT: |
359 | if (filter->block_id != VCAP_IS2) { |
360 | NL_SET_ERR_MSG_MOD(extack, |
361 | "Redirect action can only be offloaded to VCAP IS2" ); |
362 | return -EOPNOTSUPP; |
363 | } |
364 | if (filter->goto_target != -1) { |
365 | NL_SET_ERR_MSG_MOD(extack, |
366 | "Last action must be GOTO" ); |
367 | return -EOPNOTSUPP; |
368 | } |
369 | egress_port = ocelot->ops->netdev_to_port(a->dev); |
370 | if (egress_port < 0) { |
371 | NL_SET_ERR_MSG_MOD(extack, |
372 | "Destination not an ocelot port" ); |
373 | return -EOPNOTSUPP; |
374 | } |
375 | filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT; |
376 | filter->action.port_mask = BIT(egress_port); |
377 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
378 | break; |
379 | case FLOW_ACTION_MIRRED: |
380 | if (filter->block_id != VCAP_IS2) { |
381 | NL_SET_ERR_MSG_MOD(extack, |
382 | "Mirror action can only be offloaded to VCAP IS2" ); |
383 | return -EOPNOTSUPP; |
384 | } |
385 | if (filter->goto_target != -1) { |
386 | NL_SET_ERR_MSG_MOD(extack, |
387 | "Last action must be GOTO" ); |
388 | return -EOPNOTSUPP; |
389 | } |
390 | egress_port = ocelot->ops->netdev_to_port(a->dev); |
391 | if (egress_port < 0) { |
392 | NL_SET_ERR_MSG_MOD(extack, |
393 | "Destination not an ocelot port" ); |
394 | return -EOPNOTSUPP; |
395 | } |
396 | filter->egress_port.value = egress_port; |
397 | filter->action.mirror_ena = true; |
398 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
399 | break; |
400 | case FLOW_ACTION_VLAN_POP: |
401 | if (filter->block_id != VCAP_IS1) { |
402 | NL_SET_ERR_MSG_MOD(extack, |
403 | "VLAN pop action can only be offloaded to VCAP IS1" ); |
404 | return -EOPNOTSUPP; |
405 | } |
406 | if (filter->goto_target != -1) { |
407 | NL_SET_ERR_MSG_MOD(extack, |
408 | "Last action must be GOTO" ); |
409 | return -EOPNOTSUPP; |
410 | } |
411 | filter->action.vlan_pop_cnt_ena = true; |
412 | filter->action.vlan_pop_cnt++; |
413 | if (filter->action.vlan_pop_cnt > 2) { |
414 | NL_SET_ERR_MSG_MOD(extack, |
415 | "Cannot pop more than 2 VLAN headers" ); |
416 | return -EOPNOTSUPP; |
417 | } |
418 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
419 | break; |
420 | case FLOW_ACTION_VLAN_MANGLE: |
421 | if (filter->block_id == VCAP_IS1) { |
422 | err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port, |
423 | filter, a, |
424 | extack); |
425 | } else if (filter->block_id == VCAP_ES0) { |
426 | err = ocelot_flower_parse_egress_vlan_modify(filter, a, |
427 | extack); |
428 | } else { |
429 | NL_SET_ERR_MSG_MOD(extack, |
430 | "VLAN modify action can only be offloaded to VCAP IS1 or ES0" ); |
431 | err = -EOPNOTSUPP; |
432 | } |
433 | if (err) |
434 | return err; |
435 | break; |
436 | case FLOW_ACTION_PRIORITY: |
437 | if (filter->block_id != VCAP_IS1) { |
438 | NL_SET_ERR_MSG_MOD(extack, |
439 | "Priority action can only be offloaded to VCAP IS1" ); |
440 | return -EOPNOTSUPP; |
441 | } |
442 | if (filter->goto_target != -1) { |
443 | NL_SET_ERR_MSG_MOD(extack, |
444 | "Last action must be GOTO" ); |
445 | return -EOPNOTSUPP; |
446 | } |
447 | filter->action.qos_ena = true; |
448 | filter->action.qos_val = a->priority; |
449 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
450 | break; |
451 | case FLOW_ACTION_GOTO: |
452 | filter->goto_target = a->chain_index; |
453 | |
454 | if (filter->block_id == VCAP_IS1 && filter->lookup == 2) { |
455 | int pag = ocelot_chain_to_pag(chain: filter->goto_target); |
456 | |
457 | filter->action.pag_override_mask = 0xff; |
458 | filter->action.pag_val = pag; |
459 | filter->type = OCELOT_VCAP_FILTER_PAG; |
460 | } |
461 | break; |
462 | case FLOW_ACTION_VLAN_PUSH: |
463 | if (filter->block_id != VCAP_ES0) { |
464 | NL_SET_ERR_MSG_MOD(extack, |
465 | "VLAN push action can only be offloaded to VCAP ES0" ); |
466 | return -EOPNOTSUPP; |
467 | } |
468 | switch (ntohs(a->vlan.proto)) { |
469 | case ETH_P_8021Q: |
470 | tpid = OCELOT_TAG_TPID_SEL_8021Q; |
471 | break; |
472 | case ETH_P_8021AD: |
473 | tpid = OCELOT_TAG_TPID_SEL_8021AD; |
474 | break; |
475 | default: |
476 | NL_SET_ERR_MSG_MOD(extack, |
477 | "Cannot push custom TPID" ); |
478 | return -EOPNOTSUPP; |
479 | } |
480 | filter->action.tag_a_tpid_sel = tpid; |
481 | filter->action.push_outer_tag = OCELOT_ES0_TAG; |
482 | filter->action.tag_a_vid_sel = OCELOT_ES0_VID; |
483 | filter->action.vid_a_val = a->vlan.vid; |
484 | filter->action.pcp_a_val = a->vlan.prio; |
485 | filter->type = OCELOT_VCAP_FILTER_OFFLOAD; |
486 | break; |
487 | case FLOW_ACTION_GATE: |
488 | if (filter->block_id != PSFP_BLOCK_ID) { |
489 | NL_SET_ERR_MSG_MOD(extack, |
490 | "Gate action can only be offloaded to PSFP chain" ); |
491 | return -EOPNOTSUPP; |
492 | } |
493 | filter->type = OCELOT_PSFP_FILTER_OFFLOAD; |
494 | break; |
495 | default: |
496 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload action" ); |
497 | return -EOPNOTSUPP; |
498 | } |
499 | } |
500 | |
501 | if (filter->goto_target == -1) { |
502 | if ((filter->block_id == VCAP_IS2 && filter->lookup == 1) || |
503 | chain == 0 || filter->block_id == PSFP_BLOCK_ID) { |
504 | allow_missing_goto_target = true; |
505 | } else { |
506 | NL_SET_ERR_MSG_MOD(extack, "Missing GOTO action" ); |
507 | return -EOPNOTSUPP; |
508 | } |
509 | } |
510 | |
511 | if (!ocelot_is_goto_target_valid(goto_target: filter->goto_target, chain, ingress) && |
512 | !allow_missing_goto_target) { |
513 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload this GOTO target" ); |
514 | return -EOPNOTSUPP; |
515 | } |
516 | |
517 | return 0; |
518 | } |
519 | |
520 | static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port, |
521 | struct flow_cls_offload *f, |
522 | struct ocelot_vcap_filter *filter) |
523 | { |
524 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: f); |
525 | const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; |
526 | int key_length = vcap->keys[VCAP_ES0_IGR_PORT].length; |
527 | struct netlink_ext_ack *extack = f->common.extack; |
528 | struct net_device *dev, *indev; |
529 | struct flow_match_meta match; |
530 | int ingress_port; |
531 | |
532 | flow_rule_match_meta(rule, out: &match); |
533 | |
534 | if (!match.mask->ingress_ifindex) |
535 | return 0; |
536 | |
537 | if (match.mask->ingress_ifindex != 0xFFFFFFFF) { |
538 | NL_SET_ERR_MSG_MOD(extack, "Unsupported ingress ifindex mask" ); |
539 | return -EOPNOTSUPP; |
540 | } |
541 | |
542 | dev = ocelot->ops->port_to_netdev(ocelot, port); |
543 | if (!dev) |
544 | return -EINVAL; |
545 | |
546 | indev = __dev_get_by_index(net: dev_net(dev), ifindex: match.key->ingress_ifindex); |
547 | if (!indev) { |
548 | NL_SET_ERR_MSG_MOD(extack, |
549 | "Can't find the ingress port to match on" ); |
550 | return -ENOENT; |
551 | } |
552 | |
553 | ingress_port = ocelot->ops->netdev_to_port(indev); |
554 | if (ingress_port < 0) { |
555 | NL_SET_ERR_MSG_MOD(extack, |
556 | "Can only offload an ocelot ingress port" ); |
557 | return -EOPNOTSUPP; |
558 | } |
559 | if (ingress_port == port) { |
560 | NL_SET_ERR_MSG_MOD(extack, |
561 | "Ingress port is equal to the egress port" ); |
562 | return -EINVAL; |
563 | } |
564 | |
565 | filter->ingress_port.value = ingress_port; |
566 | filter->ingress_port.mask = GENMASK(key_length - 1, 0); |
567 | |
568 | return 0; |
569 | } |
570 | |
571 | static int |
572 | ocelot_flower_parse_key(struct ocelot *ocelot, int port, bool ingress, |
573 | struct flow_cls_offload *f, |
574 | struct ocelot_vcap_filter *filter) |
575 | { |
576 | struct flow_rule *rule = flow_cls_offload_flow_rule(flow_cmd: f); |
577 | struct flow_dissector *dissector = rule->match.dissector; |
578 | struct netlink_ext_ack *extack = f->common.extack; |
579 | u16 proto = ntohs(f->common.protocol); |
580 | bool match_protocol = true; |
581 | int ret; |
582 | |
583 | if (dissector->used_keys & |
584 | ~(BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) | |
585 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
586 | BIT_ULL(FLOW_DISSECTOR_KEY_META) | |
587 | BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) | |
588 | BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) | |
589 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | |
590 | BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | |
591 | BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { |
592 | return -EOPNOTSUPP; |
593 | } |
594 | |
595 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_META)) { |
596 | struct flow_match_meta match; |
597 | |
598 | flow_rule_match_meta(rule, out: &match); |
599 | if (match.mask->l2_miss) { |
600 | NL_SET_ERR_MSG_MOD(extack, "Can't match on \"l2_miss\"" ); |
601 | return -EOPNOTSUPP; |
602 | } |
603 | } |
604 | |
605 | /* For VCAP ES0 (egress rewriter) we can match on the ingress port */ |
606 | if (!ingress) { |
607 | ret = ocelot_flower_parse_indev(ocelot, port, f, filter); |
608 | if (ret) |
609 | return ret; |
610 | } |
611 | |
612 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_CONTROL)) { |
613 | struct flow_match_control match; |
614 | |
615 | flow_rule_match_control(rule, out: &match); |
616 | } |
617 | |
618 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_VLAN)) { |
619 | struct flow_match_vlan match; |
620 | |
621 | flow_rule_match_vlan(rule, out: &match); |
622 | filter->key_type = OCELOT_VCAP_KEY_ANY; |
623 | filter->vlan.vid.value = match.key->vlan_id; |
624 | filter->vlan.vid.mask = match.mask->vlan_id; |
625 | filter->vlan.pcp.value[0] = match.key->vlan_priority; |
626 | filter->vlan.pcp.mask[0] = match.mask->vlan_priority; |
627 | match_protocol = false; |
628 | } |
629 | |
630 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_ETH_ADDRS)) { |
631 | struct flow_match_eth_addrs match; |
632 | |
633 | if (filter->block_id == VCAP_ES0) { |
634 | NL_SET_ERR_MSG_MOD(extack, |
635 | "VCAP ES0 cannot match on MAC address" ); |
636 | return -EOPNOTSUPP; |
637 | } |
638 | |
639 | /* The hw support mac matches only for MAC_ETYPE key, |
640 | * therefore if other matches(port, tcp flags, etc) are added |
641 | * then just bail out |
642 | */ |
643 | if ((dissector->used_keys & |
644 | (BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
645 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
646 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) != |
647 | (BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) | |
648 | BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) | |
649 | BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) |
650 | return -EOPNOTSUPP; |
651 | |
652 | flow_rule_match_eth_addrs(rule, out: &match); |
653 | |
654 | if (filter->block_id == VCAP_IS1 && |
655 | !is_zero_ether_addr(addr: match.mask->dst)) { |
656 | NL_SET_ERR_MSG_MOD(extack, |
657 | "Key type S1_NORMAL cannot match on destination MAC" ); |
658 | return -EOPNOTSUPP; |
659 | } |
660 | |
661 | filter->key_type = OCELOT_VCAP_KEY_ETYPE; |
662 | ether_addr_copy(dst: filter->key.etype.dmac.value, |
663 | src: match.key->dst); |
664 | ether_addr_copy(dst: filter->key.etype.smac.value, |
665 | src: match.key->src); |
666 | ether_addr_copy(dst: filter->key.etype.dmac.mask, |
667 | src: match.mask->dst); |
668 | ether_addr_copy(dst: filter->key.etype.smac.mask, |
669 | src: match.mask->src); |
670 | goto finished_key_parsing; |
671 | } |
672 | |
673 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_BASIC)) { |
674 | struct flow_match_basic match; |
675 | |
676 | flow_rule_match_basic(rule, out: &match); |
677 | if (ntohs(match.key->n_proto) == ETH_P_IP) { |
678 | if (filter->block_id == VCAP_ES0) { |
679 | NL_SET_ERR_MSG_MOD(extack, |
680 | "VCAP ES0 cannot match on IP protocol" ); |
681 | return -EOPNOTSUPP; |
682 | } |
683 | |
684 | filter->key_type = OCELOT_VCAP_KEY_IPV4; |
685 | filter->key.ipv4.proto.value[0] = |
686 | match.key->ip_proto; |
687 | filter->key.ipv4.proto.mask[0] = |
688 | match.mask->ip_proto; |
689 | match_protocol = false; |
690 | } |
691 | if (ntohs(match.key->n_proto) == ETH_P_IPV6) { |
692 | if (filter->block_id == VCAP_ES0) { |
693 | NL_SET_ERR_MSG_MOD(extack, |
694 | "VCAP ES0 cannot match on IP protocol" ); |
695 | return -EOPNOTSUPP; |
696 | } |
697 | |
698 | filter->key_type = OCELOT_VCAP_KEY_IPV6; |
699 | filter->key.ipv6.proto.value[0] = |
700 | match.key->ip_proto; |
701 | filter->key.ipv6.proto.mask[0] = |
702 | match.mask->ip_proto; |
703 | match_protocol = false; |
704 | } |
705 | } |
706 | |
707 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IPV4_ADDRS) && |
708 | proto == ETH_P_IP) { |
709 | struct flow_match_ipv4_addrs match; |
710 | u8 *tmp; |
711 | |
712 | if (filter->block_id == VCAP_ES0) { |
713 | NL_SET_ERR_MSG_MOD(extack, |
714 | "VCAP ES0 cannot match on IP address" ); |
715 | return -EOPNOTSUPP; |
716 | } |
717 | |
718 | flow_rule_match_ipv4_addrs(rule, out: &match); |
719 | |
720 | if (filter->block_id == VCAP_IS1 && *(u32 *)&match.mask->dst) { |
721 | NL_SET_ERR_MSG_MOD(extack, |
722 | "Key type S1_NORMAL cannot match on destination IP" ); |
723 | return -EOPNOTSUPP; |
724 | } |
725 | |
726 | tmp = &filter->key.ipv4.sip.value.addr[0]; |
727 | memcpy(tmp, &match.key->src, 4); |
728 | |
729 | tmp = &filter->key.ipv4.sip.mask.addr[0]; |
730 | memcpy(tmp, &match.mask->src, 4); |
731 | |
732 | tmp = &filter->key.ipv4.dip.value.addr[0]; |
733 | memcpy(tmp, &match.key->dst, 4); |
734 | |
735 | tmp = &filter->key.ipv4.dip.mask.addr[0]; |
736 | memcpy(tmp, &match.mask->dst, 4); |
737 | match_protocol = false; |
738 | } |
739 | |
740 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_IPV6_ADDRS) && |
741 | proto == ETH_P_IPV6) { |
742 | return -EOPNOTSUPP; |
743 | } |
744 | |
745 | if (flow_rule_match_key(rule, key: FLOW_DISSECTOR_KEY_PORTS)) { |
746 | struct flow_match_ports match; |
747 | |
748 | if (filter->block_id == VCAP_ES0) { |
749 | NL_SET_ERR_MSG_MOD(extack, |
750 | "VCAP ES0 cannot match on L4 ports" ); |
751 | return -EOPNOTSUPP; |
752 | } |
753 | |
754 | flow_rule_match_ports(rule, out: &match); |
755 | filter->key.ipv4.sport.value = ntohs(match.key->src); |
756 | filter->key.ipv4.sport.mask = ntohs(match.mask->src); |
757 | filter->key.ipv4.dport.value = ntohs(match.key->dst); |
758 | filter->key.ipv4.dport.mask = ntohs(match.mask->dst); |
759 | match_protocol = false; |
760 | } |
761 | |
762 | finished_key_parsing: |
763 | if (match_protocol && proto != ETH_P_ALL) { |
764 | if (filter->block_id == VCAP_ES0) { |
765 | NL_SET_ERR_MSG_MOD(extack, |
766 | "VCAP ES0 cannot match on L2 proto" ); |
767 | return -EOPNOTSUPP; |
768 | } |
769 | |
770 | /* TODO: support SNAP, LLC etc */ |
771 | if (proto < ETH_P_802_3_MIN) |
772 | return -EOPNOTSUPP; |
773 | filter->key_type = OCELOT_VCAP_KEY_ETYPE; |
774 | *(__be16 *)filter->key.etype.etype.value = htons(proto); |
775 | *(__be16 *)filter->key.etype.etype.mask = htons(0xffff); |
776 | } |
777 | /* else, a filter of type OCELOT_VCAP_KEY_ANY is implicitly added */ |
778 | |
779 | return 0; |
780 | } |
781 | |
782 | static int ocelot_flower_parse(struct ocelot *ocelot, int port, bool ingress, |
783 | struct flow_cls_offload *f, |
784 | struct ocelot_vcap_filter *filter) |
785 | { |
786 | int ret; |
787 | |
788 | filter->prio = f->common.prio; |
789 | filter->id.cookie = f->cookie; |
790 | filter->id.tc_offload = true; |
791 | |
792 | ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter); |
793 | if (ret) |
794 | return ret; |
795 | |
796 | /* PSFP filter need to parse key by stream identification function. */ |
797 | if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) |
798 | return 0; |
799 | |
800 | return ocelot_flower_parse_key(ocelot, port, ingress, f, filter); |
801 | } |
802 | |
803 | static struct ocelot_vcap_filter |
804 | *ocelot_vcap_filter_create(struct ocelot *ocelot, int port, bool ingress, |
805 | struct flow_cls_offload *f) |
806 | { |
807 | struct ocelot_vcap_filter *filter; |
808 | |
809 | filter = kzalloc(size: sizeof(*filter), GFP_KERNEL); |
810 | if (!filter) |
811 | return NULL; |
812 | |
813 | if (ingress) { |
814 | filter->ingress_port_mask = BIT(port); |
815 | } else { |
816 | const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; |
817 | int key_length = vcap->keys[VCAP_ES0_EGR_PORT].length; |
818 | |
819 | filter->egress_port.value = port; |
820 | filter->egress_port.mask = GENMASK(key_length - 1, 0); |
821 | } |
822 | |
823 | return filter; |
824 | } |
825 | |
826 | static int ocelot_vcap_dummy_filter_add(struct ocelot *ocelot, |
827 | struct ocelot_vcap_filter *filter) |
828 | { |
829 | list_add(new: &filter->list, head: &ocelot->dummy_rules); |
830 | |
831 | return 0; |
832 | } |
833 | |
834 | static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot, |
835 | struct ocelot_vcap_filter *filter) |
836 | { |
837 | list_del(entry: &filter->list); |
838 | kfree(objp: filter); |
839 | |
840 | return 0; |
841 | } |
842 | |
843 | /* If we have an egress VLAN modification rule, we need to actually write the |
844 | * delta between the input VLAN (from the key) and the output VLAN (from the |
845 | * action), but the action was parsed first. So we need to patch the delta into |
846 | * the action here. |
847 | */ |
848 | static int |
849 | ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter, |
850 | struct netlink_ext_ack *extack) |
851 | { |
852 | if (filter->block_id != VCAP_ES0 || |
853 | filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID) |
854 | return 0; |
855 | |
856 | if (filter->vlan.vid.mask != VLAN_VID_MASK) { |
857 | NL_SET_ERR_MSG_MOD(extack, |
858 | "VCAP ES0 VLAN rewriting needs a full VLAN in the key" ); |
859 | return -EOPNOTSUPP; |
860 | } |
861 | |
862 | filter->action.vid_a_val -= filter->vlan.vid.value; |
863 | filter->action.vid_a_val &= VLAN_VID_MASK; |
864 | |
865 | return 0; |
866 | } |
867 | |
868 | int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, |
869 | struct flow_cls_offload *f, bool ingress) |
870 | { |
871 | struct netlink_ext_ack *extack = f->common.extack; |
872 | struct ocelot_vcap_filter *filter; |
873 | int chain = f->common.chain_index; |
874 | int block_id, ret; |
875 | |
876 | if (chain && !ocelot_find_vcap_filter_that_points_at(ocelot, chain)) { |
877 | NL_SET_ERR_MSG_MOD(extack, "No default GOTO action points to this chain" ); |
878 | return -EOPNOTSUPP; |
879 | } |
880 | |
881 | block_id = ocelot_chain_to_block(chain, ingress); |
882 | if (block_id < 0) { |
883 | NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain" ); |
884 | return -EOPNOTSUPP; |
885 | } |
886 | |
887 | filter = ocelot_vcap_block_find_filter_by_id(block: &ocelot->block[block_id], |
888 | cookie: f->cookie, tc_offload: true); |
889 | if (filter) { |
890 | /* Filter already exists on other ports */ |
891 | if (!ingress) { |
892 | NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 does not support shared filters" ); |
893 | return -EOPNOTSUPP; |
894 | } |
895 | |
896 | filter->ingress_port_mask |= BIT(port); |
897 | |
898 | return ocelot_vcap_filter_replace(ocelot, filter); |
899 | } |
900 | |
901 | /* Filter didn't exist, create it now */ |
902 | filter = ocelot_vcap_filter_create(ocelot, port, ingress, f); |
903 | if (!filter) |
904 | return -ENOMEM; |
905 | |
906 | ret = ocelot_flower_parse(ocelot, port, ingress, f, filter); |
907 | if (ret) { |
908 | kfree(objp: filter); |
909 | return ret; |
910 | } |
911 | |
912 | ret = ocelot_flower_patch_es0_vlan_modify(filter, extack); |
913 | if (ret) { |
914 | kfree(objp: filter); |
915 | return ret; |
916 | } |
917 | |
918 | /* The non-optional GOTOs for the TCAM skeleton don't need |
919 | * to be actually offloaded. |
920 | */ |
921 | if (filter->type == OCELOT_VCAP_FILTER_DUMMY) |
922 | return ocelot_vcap_dummy_filter_add(ocelot, filter); |
923 | |
924 | if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) { |
925 | kfree(objp: filter); |
926 | if (ocelot->ops->psfp_filter_add) |
927 | return ocelot->ops->psfp_filter_add(ocelot, port, f); |
928 | |
929 | NL_SET_ERR_MSG_MOD(extack, "PSFP chain is not supported in HW" ); |
930 | return -EOPNOTSUPP; |
931 | } |
932 | |
933 | return ocelot_vcap_filter_add(ocelot, rule: filter, extack: f->common.extack); |
934 | } |
935 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_replace); |
936 | |
937 | int ocelot_cls_flower_destroy(struct ocelot *ocelot, int port, |
938 | struct flow_cls_offload *f, bool ingress) |
939 | { |
940 | struct ocelot_vcap_filter *filter; |
941 | struct ocelot_vcap_block *block; |
942 | int block_id; |
943 | |
944 | block_id = ocelot_chain_to_block(chain: f->common.chain_index, ingress); |
945 | if (block_id < 0) |
946 | return 0; |
947 | |
948 | if (block_id == PSFP_BLOCK_ID) { |
949 | if (ocelot->ops->psfp_filter_del) |
950 | return ocelot->ops->psfp_filter_del(ocelot, f); |
951 | |
952 | return -EOPNOTSUPP; |
953 | } |
954 | |
955 | block = &ocelot->block[block_id]; |
956 | |
957 | filter = ocelot_vcap_block_find_filter_by_id(block, cookie: f->cookie, tc_offload: true); |
958 | if (!filter) |
959 | return 0; |
960 | |
961 | if (filter->type == OCELOT_VCAP_FILTER_DUMMY) |
962 | return ocelot_vcap_dummy_filter_del(ocelot, filter); |
963 | |
964 | if (ingress) { |
965 | filter->ingress_port_mask &= ~BIT(port); |
966 | if (filter->ingress_port_mask) |
967 | return ocelot_vcap_filter_replace(ocelot, filter); |
968 | } |
969 | |
970 | return ocelot_vcap_filter_del(ocelot, rule: filter); |
971 | } |
972 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_destroy); |
973 | |
974 | int ocelot_cls_flower_stats(struct ocelot *ocelot, int port, |
975 | struct flow_cls_offload *f, bool ingress) |
976 | { |
977 | struct ocelot_vcap_filter *filter; |
978 | struct ocelot_vcap_block *block; |
979 | struct flow_stats stats = {0}; |
980 | int block_id, ret; |
981 | |
982 | block_id = ocelot_chain_to_block(chain: f->common.chain_index, ingress); |
983 | if (block_id < 0) |
984 | return 0; |
985 | |
986 | if (block_id == PSFP_BLOCK_ID) { |
987 | if (ocelot->ops->psfp_stats_get) { |
988 | ret = ocelot->ops->psfp_stats_get(ocelot, f, &stats); |
989 | if (ret) |
990 | return ret; |
991 | |
992 | goto stats_update; |
993 | } |
994 | |
995 | return -EOPNOTSUPP; |
996 | } |
997 | |
998 | block = &ocelot->block[block_id]; |
999 | |
1000 | filter = ocelot_vcap_block_find_filter_by_id(block, cookie: f->cookie, tc_offload: true); |
1001 | if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY) |
1002 | return 0; |
1003 | |
1004 | ret = ocelot_vcap_filter_stats_update(ocelot, rule: filter); |
1005 | if (ret) |
1006 | return ret; |
1007 | |
1008 | stats.pkts = filter->stats.pkts; |
1009 | |
1010 | stats_update: |
1011 | flow_stats_update(flow_stats: &f->stats, bytes: 0x0, pkts: stats.pkts, drops: stats.drops, lastused: 0x0, |
1012 | used_hw_stats: FLOW_ACTION_HW_STATS_IMMEDIATE); |
1013 | return 0; |
1014 | } |
1015 | EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats); |
1016 | |