1 | /* |
2 | * This file is part of the Chelsio T4 Ethernet driver for Linux. |
3 | * |
4 | * Copyright (c) 2016 Chelsio Communications, Inc. All rights reserved. |
5 | * |
6 | * This software is available to you under a choice of one of two |
7 | * licenses. You may choose to be licensed under the terms of the GNU |
8 | * General Public License (GPL) Version 2, available from the file |
9 | * COPYING in the main directory of this source tree, or the |
10 | * OpenIB.org BSD license below: |
11 | * |
12 | * Redistribution and use in source and binary forms, with or |
13 | * without modification, are permitted provided that the following |
14 | * conditions are met: |
15 | * |
16 | * - Redistributions of source code must retain the above |
17 | * copyright notice, this list of conditions and the following |
18 | * disclaimer. |
19 | * |
20 | * - Redistributions in binary form must reproduce the above |
21 | * copyright notice, this list of conditions and the following |
22 | * disclaimer in the documentation and/or other materials |
23 | * provided with the distribution. |
24 | * |
25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
29 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
30 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
31 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
32 | * SOFTWARE. |
33 | */ |
34 | |
35 | #include <net/tc_act/tc_gact.h> |
36 | #include <net/tc_act/tc_mirred.h> |
37 | |
38 | #include "cxgb4.h" |
39 | #include "cxgb4_filter.h" |
40 | #include "cxgb4_tc_u32_parse.h" |
41 | #include "cxgb4_tc_u32.h" |
42 | |
43 | /* Fill ch_filter_specification with parsed match value/mask pair. */ |
44 | static int fill_match_fields(struct adapter *adap, |
45 | struct ch_filter_specification *fs, |
46 | struct tc_cls_u32_offload *cls, |
47 | const struct cxgb4_match_field *entry, |
48 | bool ) |
49 | { |
50 | unsigned int i, j; |
51 | __be32 val, mask; |
52 | int off, err; |
53 | bool found; |
54 | |
55 | for (i = 0; i < cls->knode.sel->nkeys; i++) { |
56 | off = cls->knode.sel->keys[i].off; |
57 | val = cls->knode.sel->keys[i].val; |
58 | mask = cls->knode.sel->keys[i].mask; |
59 | |
60 | if (next_header) { |
61 | /* For next headers, parse only keys with offmask */ |
62 | if (!cls->knode.sel->keys[i].offmask) |
63 | continue; |
64 | } else { |
65 | /* For the remaining, parse only keys without offmask */ |
66 | if (cls->knode.sel->keys[i].offmask) |
67 | continue; |
68 | } |
69 | |
70 | found = false; |
71 | |
72 | for (j = 0; entry[j].val; j++) { |
73 | if (off == entry[j].off) { |
74 | found = true; |
75 | err = entry[j].val(fs, val, mask); |
76 | if (err) |
77 | return err; |
78 | break; |
79 | } |
80 | } |
81 | |
82 | if (!found) |
83 | return -EINVAL; |
84 | } |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | /* Fill ch_filter_specification with parsed action. */ |
90 | static int fill_action_fields(struct adapter *adap, |
91 | struct ch_filter_specification *fs, |
92 | struct tc_cls_u32_offload *cls) |
93 | { |
94 | unsigned int num_actions = 0; |
95 | const struct tc_action *a; |
96 | struct tcf_exts *exts; |
97 | int i; |
98 | |
99 | exts = cls->knode.exts; |
100 | if (!tcf_exts_has_actions(exts)) |
101 | return -EINVAL; |
102 | |
103 | tcf_exts_for_each_action(i, a, exts) { |
104 | /* Don't allow more than one action per rule. */ |
105 | if (num_actions) |
106 | return -EINVAL; |
107 | |
108 | /* Drop in hardware. */ |
109 | if (is_tcf_gact_shot(a)) { |
110 | fs->action = FILTER_DROP; |
111 | num_actions++; |
112 | continue; |
113 | } |
114 | |
115 | /* Re-direct to specified port in hardware. */ |
116 | if (is_tcf_mirred_egress_redirect(a)) { |
117 | struct net_device *n_dev, *target_dev; |
118 | bool found = false; |
119 | unsigned int i; |
120 | |
121 | target_dev = tcf_mirred_dev(a); |
122 | for_each_port(adap, i) { |
123 | n_dev = adap->port[i]; |
124 | if (target_dev == n_dev) { |
125 | fs->action = FILTER_SWITCH; |
126 | fs->eport = i; |
127 | found = true; |
128 | break; |
129 | } |
130 | } |
131 | |
132 | /* Interface doesn't belong to any port of |
133 | * the underlying hardware. |
134 | */ |
135 | if (!found) |
136 | return -EINVAL; |
137 | |
138 | num_actions++; |
139 | continue; |
140 | } |
141 | |
142 | /* Un-supported action. */ |
143 | return -EINVAL; |
144 | } |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | int cxgb4_config_knode(struct net_device *dev, struct tc_cls_u32_offload *cls) |
150 | { |
151 | const struct cxgb4_match_field *start, *link_start = NULL; |
152 | struct netlink_ext_ack *extack = cls->common.extack; |
153 | struct adapter *adapter = netdev2adap(dev); |
154 | __be16 protocol = cls->common.protocol; |
155 | struct ch_filter_specification fs; |
156 | struct cxgb4_tc_u32_table *t; |
157 | struct cxgb4_link *link; |
158 | u32 uhtid, link_uhtid; |
159 | bool is_ipv6 = false; |
160 | u8 inet_family; |
161 | int filter_id; |
162 | int ret; |
163 | |
164 | if (!can_tc_u32_offload(dev)) |
165 | return -EOPNOTSUPP; |
166 | |
167 | if (protocol != htons(ETH_P_IP) && protocol != htons(ETH_P_IPV6)) |
168 | return -EOPNOTSUPP; |
169 | |
170 | inet_family = (protocol == htons(ETH_P_IPV6)) ? PF_INET6 : PF_INET; |
171 | |
172 | /* Get a free filter entry TID, where we can insert this new |
173 | * rule. Only insert rule if its prio doesn't conflict with |
174 | * existing rules. |
175 | */ |
176 | filter_id = cxgb4_get_free_ftid(dev, family: inet_family, hash_en: false, |
177 | TC_U32_NODE(cls->knode.handle)); |
178 | if (filter_id < 0) { |
179 | NL_SET_ERR_MSG_MOD(extack, |
180 | "No free LETCAM index available" ); |
181 | return -ENOMEM; |
182 | } |
183 | |
184 | t = adapter->tc_u32; |
185 | uhtid = TC_U32_USERHTID(cls->knode.handle); |
186 | link_uhtid = TC_U32_USERHTID(cls->knode.link_handle); |
187 | |
188 | /* Ensure that uhtid is either root u32 (i.e. 0x800) |
189 | * or a a valid linked bucket. |
190 | */ |
191 | if (uhtid != 0x800 && uhtid >= t->size) |
192 | return -EINVAL; |
193 | |
194 | /* Ensure link handle uhtid is sane, if specified. */ |
195 | if (link_uhtid >= t->size) |
196 | return -EINVAL; |
197 | |
198 | memset(&fs, 0, sizeof(fs)); |
199 | |
200 | if (filter_id < adapter->tids.nhpftids) |
201 | fs.prio = 1; |
202 | fs.tc_prio = cls->common.prio; |
203 | fs.tc_cookie = cls->knode.handle; |
204 | |
205 | if (protocol == htons(ETH_P_IPV6)) { |
206 | start = cxgb4_ipv6_fields; |
207 | is_ipv6 = true; |
208 | } else { |
209 | start = cxgb4_ipv4_fields; |
210 | is_ipv6 = false; |
211 | } |
212 | |
213 | if (uhtid != 0x800) { |
214 | /* Link must exist from root node before insertion. */ |
215 | if (!t->table[uhtid - 1].link_handle) |
216 | return -EINVAL; |
217 | |
218 | /* Link must have a valid supported next header. */ |
219 | link_start = t->table[uhtid - 1].match_field; |
220 | if (!link_start) |
221 | return -EINVAL; |
222 | } |
223 | |
224 | /* Parse links and record them for subsequent jumps to valid |
225 | * next headers. |
226 | */ |
227 | if (link_uhtid) { |
228 | const struct cxgb4_next_header *next; |
229 | bool found = false; |
230 | unsigned int i, j; |
231 | __be32 val, mask; |
232 | int off; |
233 | |
234 | if (t->table[link_uhtid - 1].link_handle) { |
235 | dev_err(adapter->pdev_dev, |
236 | "Link handle exists for: 0x%x\n" , |
237 | link_uhtid); |
238 | return -EINVAL; |
239 | } |
240 | |
241 | next = is_ipv6 ? cxgb4_ipv6_jumps : cxgb4_ipv4_jumps; |
242 | |
243 | /* Try to find matches that allow jumps to next header. */ |
244 | for (i = 0; next[i].jump; i++) { |
245 | if (next[i].sel.offoff != cls->knode.sel->offoff || |
246 | next[i].sel.offshift != cls->knode.sel->offshift || |
247 | next[i].sel.offmask != cls->knode.sel->offmask || |
248 | next[i].sel.off != cls->knode.sel->off) |
249 | continue; |
250 | |
251 | /* Found a possible candidate. Find a key that |
252 | * matches the corresponding offset, value, and |
253 | * mask to jump to next header. |
254 | */ |
255 | for (j = 0; j < cls->knode.sel->nkeys; j++) { |
256 | off = cls->knode.sel->keys[j].off; |
257 | val = cls->knode.sel->keys[j].val; |
258 | mask = cls->knode.sel->keys[j].mask; |
259 | |
260 | if (next[i].key.off == off && |
261 | next[i].key.val == val && |
262 | next[i].key.mask == mask) { |
263 | found = true; |
264 | break; |
265 | } |
266 | } |
267 | |
268 | if (!found) |
269 | continue; /* Try next candidate. */ |
270 | |
271 | /* Candidate to jump to next header found. |
272 | * Translate all keys to internal specification |
273 | * and store them in jump table. This spec is copied |
274 | * later to set the actual filters. |
275 | */ |
276 | ret = fill_match_fields(adap: adapter, fs: &fs, cls, |
277 | entry: start, next_header: false); |
278 | if (ret) |
279 | goto out; |
280 | |
281 | link = &t->table[link_uhtid - 1]; |
282 | link->match_field = next[i].jump; |
283 | link->link_handle = cls->knode.handle; |
284 | memcpy(&link->fs, &fs, sizeof(fs)); |
285 | break; |
286 | } |
287 | |
288 | /* No candidate found to jump to next header. */ |
289 | if (!found) |
290 | return -EINVAL; |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | /* Fill ch_filter_specification match fields to be shipped to hardware. |
296 | * Copy the linked spec (if any) first. And then update the spec as |
297 | * needed. |
298 | */ |
299 | if (uhtid != 0x800 && t->table[uhtid - 1].link_handle) { |
300 | /* Copy linked ch_filter_specification */ |
301 | memcpy(&fs, &t->table[uhtid - 1].fs, sizeof(fs)); |
302 | ret = fill_match_fields(adap: adapter, fs: &fs, cls, |
303 | entry: link_start, next_header: true); |
304 | if (ret) |
305 | goto out; |
306 | } |
307 | |
308 | ret = fill_match_fields(adap: adapter, fs: &fs, cls, entry: start, next_header: false); |
309 | if (ret) |
310 | goto out; |
311 | |
312 | /* Fill ch_filter_specification action fields to be shipped to |
313 | * hardware. |
314 | */ |
315 | ret = fill_action_fields(adap: adapter, fs: &fs, cls); |
316 | if (ret) |
317 | goto out; |
318 | |
319 | /* The filter spec has been completely built from the info |
320 | * provided from u32. We now set some default fields in the |
321 | * spec for sanity. |
322 | */ |
323 | |
324 | /* Match only packets coming from the ingress port where this |
325 | * filter will be created. |
326 | */ |
327 | fs.val.iport = netdev2pinfo(dev)->port_id; |
328 | fs.mask.iport = ~0; |
329 | |
330 | /* Enable filter hit counts. */ |
331 | fs.hitcnts = 1; |
332 | |
333 | /* Set type of filter - IPv6 or IPv4 */ |
334 | fs.type = is_ipv6 ? 1 : 0; |
335 | |
336 | /* Set the filter */ |
337 | ret = cxgb4_set_filter(dev, filter_id, fs: &fs); |
338 | if (ret) |
339 | goto out; |
340 | |
341 | /* If this is a linked bucket, then set the corresponding |
342 | * entry in the bitmap to mark it as belonging to this linked |
343 | * bucket. |
344 | */ |
345 | if (uhtid != 0x800 && t->table[uhtid - 1].link_handle) |
346 | set_bit(nr: filter_id, addr: t->table[uhtid - 1].tid_map); |
347 | |
348 | out: |
349 | return ret; |
350 | } |
351 | |
352 | int cxgb4_delete_knode(struct net_device *dev, struct tc_cls_u32_offload *cls) |
353 | { |
354 | struct adapter *adapter = netdev2adap(dev); |
355 | unsigned int filter_id, max_tids, i, j; |
356 | struct cxgb4_link *link = NULL; |
357 | struct cxgb4_tc_u32_table *t; |
358 | struct filter_entry *f; |
359 | bool found = false; |
360 | u32 handle, uhtid; |
361 | u8 nslots; |
362 | int ret; |
363 | |
364 | if (!can_tc_u32_offload(dev)) |
365 | return -EOPNOTSUPP; |
366 | |
367 | /* Fetch the location to delete the filter. */ |
368 | max_tids = adapter->tids.nhpftids + adapter->tids.nftids; |
369 | |
370 | spin_lock_bh(lock: &adapter->tids.ftid_lock); |
371 | filter_id = 0; |
372 | while (filter_id < max_tids) { |
373 | if (filter_id < adapter->tids.nhpftids) { |
374 | i = filter_id; |
375 | f = &adapter->tids.hpftid_tab[i]; |
376 | if (f->valid && f->fs.tc_cookie == cls->knode.handle) { |
377 | found = true; |
378 | break; |
379 | } |
380 | |
381 | i = find_next_bit(addr: adapter->tids.hpftid_bmap, |
382 | size: adapter->tids.nhpftids, offset: i + 1); |
383 | if (i >= adapter->tids.nhpftids) { |
384 | filter_id = adapter->tids.nhpftids; |
385 | continue; |
386 | } |
387 | |
388 | filter_id = i; |
389 | } else { |
390 | i = filter_id - adapter->tids.nhpftids; |
391 | f = &adapter->tids.ftid_tab[i]; |
392 | if (f->valid && f->fs.tc_cookie == cls->knode.handle) { |
393 | found = true; |
394 | break; |
395 | } |
396 | |
397 | i = find_next_bit(addr: adapter->tids.ftid_bmap, |
398 | size: adapter->tids.nftids, offset: i + 1); |
399 | if (i >= adapter->tids.nftids) |
400 | break; |
401 | |
402 | filter_id = i + adapter->tids.nhpftids; |
403 | } |
404 | |
405 | nslots = 0; |
406 | if (f->fs.type) { |
407 | nslots++; |
408 | if (CHELSIO_CHIP_VERSION(adapter->params.chip) < |
409 | CHELSIO_T6) |
410 | nslots += 2; |
411 | } |
412 | |
413 | filter_id += nslots; |
414 | } |
415 | spin_unlock_bh(lock: &adapter->tids.ftid_lock); |
416 | |
417 | if (!found) |
418 | return -ERANGE; |
419 | |
420 | t = adapter->tc_u32; |
421 | handle = cls->knode.handle; |
422 | uhtid = TC_U32_USERHTID(cls->knode.handle); |
423 | |
424 | /* Ensure that uhtid is either root u32 (i.e. 0x800) |
425 | * or a a valid linked bucket. |
426 | */ |
427 | if (uhtid != 0x800 && uhtid >= t->size) |
428 | return -EINVAL; |
429 | |
430 | /* Delete the specified filter */ |
431 | if (uhtid != 0x800) { |
432 | link = &t->table[uhtid - 1]; |
433 | if (!link->link_handle) |
434 | return -EINVAL; |
435 | |
436 | if (!test_bit(filter_id, link->tid_map)) |
437 | return -EINVAL; |
438 | } |
439 | |
440 | ret = cxgb4_del_filter(dev, filter_id, NULL); |
441 | if (ret) |
442 | goto out; |
443 | |
444 | if (link) |
445 | clear_bit(nr: filter_id, addr: link->tid_map); |
446 | |
447 | /* If a link is being deleted, then delete all filters |
448 | * associated with the link. |
449 | */ |
450 | for (i = 0; i < t->size; i++) { |
451 | link = &t->table[i]; |
452 | |
453 | if (link->link_handle == handle) { |
454 | for (j = 0; j < max_tids; j++) { |
455 | if (!test_bit(j, link->tid_map)) |
456 | continue; |
457 | |
458 | ret = __cxgb4_del_filter(dev, filter_id: j, NULL, NULL); |
459 | if (ret) |
460 | goto out; |
461 | |
462 | clear_bit(nr: j, addr: link->tid_map); |
463 | } |
464 | |
465 | /* Clear the link state */ |
466 | link->match_field = NULL; |
467 | link->link_handle = 0; |
468 | memset(&link->fs, 0, sizeof(link->fs)); |
469 | break; |
470 | } |
471 | } |
472 | |
473 | out: |
474 | return ret; |
475 | } |
476 | |
477 | void cxgb4_cleanup_tc_u32(struct adapter *adap) |
478 | { |
479 | struct cxgb4_tc_u32_table *t; |
480 | unsigned int i; |
481 | |
482 | if (!adap->tc_u32) |
483 | return; |
484 | |
485 | /* Free up all allocated memory. */ |
486 | t = adap->tc_u32; |
487 | for (i = 0; i < t->size; i++) { |
488 | struct cxgb4_link *link = &t->table[i]; |
489 | |
490 | kvfree(addr: link->tid_map); |
491 | } |
492 | kvfree(addr: adap->tc_u32); |
493 | } |
494 | |
495 | struct cxgb4_tc_u32_table *cxgb4_init_tc_u32(struct adapter *adap) |
496 | { |
497 | unsigned int max_tids = adap->tids.nftids + adap->tids.nhpftids; |
498 | struct cxgb4_tc_u32_table *t; |
499 | unsigned int i; |
500 | |
501 | if (!max_tids) |
502 | return NULL; |
503 | |
504 | t = kvzalloc(struct_size(t, table, max_tids), GFP_KERNEL); |
505 | if (!t) |
506 | return NULL; |
507 | |
508 | t->size = max_tids; |
509 | |
510 | for (i = 0; i < t->size; i++) { |
511 | struct cxgb4_link *link = &t->table[i]; |
512 | unsigned int bmap_size; |
513 | |
514 | bmap_size = BITS_TO_LONGS(max_tids); |
515 | link->tid_map = kvcalloc(n: bmap_size, size: sizeof(unsigned long), |
516 | GFP_KERNEL); |
517 | if (!link->tid_map) |
518 | goto out_no_mem; |
519 | bitmap_zero(dst: link->tid_map, nbits: max_tids); |
520 | } |
521 | |
522 | return t; |
523 | |
524 | out_no_mem: |
525 | for (i = 0; i < t->size; i++) { |
526 | struct cxgb4_link *link = &t->table[i]; |
527 | kvfree(addr: link->tid_map); |
528 | } |
529 | kvfree(addr: t); |
530 | |
531 | return NULL; |
532 | } |
533 | |