diff options
Diffstat (limited to 'net/core/ethtool.c')
-rw-r--r-- | net/core/ethtool.c | 329 |
1 files changed, 328 insertions, 1 deletions
diff --git a/net/core/ethtool.c b/net/core/ethtool.c index d8aee584e8d1..6ec73d3983a3 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c | |||
@@ -120,7 +120,7 @@ int ethtool_op_set_ufo(struct net_device *dev, u32 data) | |||
120 | * NETIF_F_xxx values in include/linux/netdevice.h | 120 | * NETIF_F_xxx values in include/linux/netdevice.h |
121 | */ | 121 | */ |
122 | static const u32 flags_dup_features = | 122 | static const u32 flags_dup_features = |
123 | ETH_FLAG_LRO; | 123 | (ETH_FLAG_LRO | ETH_FLAG_NTUPLE); |
124 | 124 | ||
125 | u32 ethtool_op_get_flags(struct net_device *dev) | 125 | u32 ethtool_op_get_flags(struct net_device *dev) |
126 | { | 126 | { |
@@ -139,9 +139,26 @@ int ethtool_op_set_flags(struct net_device *dev, u32 data) | |||
139 | else | 139 | else |
140 | dev->features &= ~NETIF_F_LRO; | 140 | dev->features &= ~NETIF_F_LRO; |
141 | 141 | ||
142 | if (data & ETH_FLAG_NTUPLE) | ||
143 | dev->features |= NETIF_F_NTUPLE; | ||
144 | else | ||
145 | dev->features &= ~NETIF_F_NTUPLE; | ||
146 | |||
142 | return 0; | 147 | return 0; |
143 | } | 148 | } |
144 | 149 | ||
150 | void ethtool_ntuple_flush(struct net_device *dev) | ||
151 | { | ||
152 | struct ethtool_rx_ntuple_flow_spec_container *fsc, *f; | ||
153 | |||
154 | list_for_each_entry_safe(fsc, f, &dev->ethtool_ntuple_list.list, list) { | ||
155 | list_del(&fsc->list); | ||
156 | kfree(fsc); | ||
157 | } | ||
158 | dev->ethtool_ntuple_list.count = 0; | ||
159 | } | ||
160 | EXPORT_SYMBOL(ethtool_ntuple_flush); | ||
161 | |||
145 | /* Handlers for each ethtool command */ | 162 | /* Handlers for each ethtool command */ |
146 | 163 | ||
147 | static int ethtool_get_settings(struct net_device *dev, void __user *useraddr) | 164 | static int ethtool_get_settings(struct net_device *dev, void __user *useraddr) |
@@ -266,6 +283,307 @@ err_out: | |||
266 | return ret; | 283 | return ret; |
267 | } | 284 | } |
268 | 285 | ||
286 | static int __rx_ntuple_filter_add(struct ethtool_rx_ntuple_list *list, | ||
287 | struct ethtool_rx_ntuple_flow_spec *spec) | ||
288 | { | ||
289 | struct ethtool_rx_ntuple_flow_spec_container *fsc; | ||
290 | |||
291 | /* don't add filters forever */ | ||
292 | if (list->count >= ETHTOOL_MAX_NTUPLE_LIST_ENTRY) | ||
293 | return 0; | ||
294 | |||
295 | fsc = kmalloc(sizeof(*fsc), GFP_ATOMIC); | ||
296 | if (!fsc) | ||
297 | return -ENOMEM; | ||
298 | |||
299 | /* Copy the whole filter over */ | ||
300 | fsc->fs.flow_type = spec->flow_type; | ||
301 | memcpy(&fsc->fs.h_u, &spec->h_u, sizeof(spec->h_u)); | ||
302 | memcpy(&fsc->fs.m_u, &spec->m_u, sizeof(spec->m_u)); | ||
303 | |||
304 | fsc->fs.vlan_tag = spec->vlan_tag; | ||
305 | fsc->fs.vlan_tag_mask = spec->vlan_tag_mask; | ||
306 | fsc->fs.data = spec->data; | ||
307 | fsc->fs.data_mask = spec->data_mask; | ||
308 | fsc->fs.action = spec->action; | ||
309 | |||
310 | /* add to the list */ | ||
311 | list_add_tail_rcu(&fsc->list, &list->list); | ||
312 | list->count++; | ||
313 | |||
314 | return 0; | ||
315 | } | ||
316 | |||
317 | static int ethtool_set_rx_ntuple(struct net_device *dev, void __user *useraddr) | ||
318 | { | ||
319 | struct ethtool_rx_ntuple cmd; | ||
320 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
321 | int ret; | ||
322 | |||
323 | if (!ops->set_rx_ntuple) | ||
324 | return -EOPNOTSUPP; | ||
325 | |||
326 | if (!(dev->features & NETIF_F_NTUPLE)) | ||
327 | return -EINVAL; | ||
328 | |||
329 | if (copy_from_user(&cmd, useraddr, sizeof(cmd))) | ||
330 | return -EFAULT; | ||
331 | |||
332 | ret = ops->set_rx_ntuple(dev, &cmd); | ||
333 | |||
334 | /* | ||
335 | * Cache filter in dev struct for GET operation only if | ||
336 | * the underlying driver doesn't have its own GET operation, and | ||
337 | * only if the filter was added successfully. | ||
338 | */ | ||
339 | if (!ops->get_rx_ntuple && !ret) | ||
340 | if (__rx_ntuple_filter_add(&dev->ethtool_ntuple_list, &cmd.fs)) | ||
341 | return -ENOMEM; | ||
342 | |||
343 | return ret; | ||
344 | } | ||
345 | |||
346 | static int ethtool_get_rx_ntuple(struct net_device *dev, void __user *useraddr) | ||
347 | { | ||
348 | struct ethtool_gstrings gstrings; | ||
349 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
350 | struct ethtool_rx_ntuple_flow_spec_container *fsc; | ||
351 | u8 *data; | ||
352 | char *p; | ||
353 | int ret, i, num_strings = 0; | ||
354 | |||
355 | if (!ops->get_sset_count) | ||
356 | return -EOPNOTSUPP; | ||
357 | |||
358 | if (copy_from_user(&gstrings, useraddr, sizeof(gstrings))) | ||
359 | return -EFAULT; | ||
360 | |||
361 | ret = ops->get_sset_count(dev, gstrings.string_set); | ||
362 | if (ret < 0) | ||
363 | return ret; | ||
364 | |||
365 | gstrings.len = ret; | ||
366 | |||
367 | data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER); | ||
368 | if (!data) | ||
369 | return -ENOMEM; | ||
370 | |||
371 | if (ops->get_rx_ntuple) { | ||
372 | /* driver-specific filter grab */ | ||
373 | ret = ops->get_rx_ntuple(dev, gstrings.string_set, data); | ||
374 | goto copy; | ||
375 | } | ||
376 | |||
377 | /* default ethtool filter grab */ | ||
378 | i = 0; | ||
379 | p = (char *)data; | ||
380 | list_for_each_entry(fsc, &dev->ethtool_ntuple_list.list, list) { | ||
381 | sprintf(p, "Filter %d:\n", i); | ||
382 | p += ETH_GSTRING_LEN; | ||
383 | num_strings++; | ||
384 | |||
385 | switch (fsc->fs.flow_type) { | ||
386 | case TCP_V4_FLOW: | ||
387 | sprintf(p, "\tFlow Type: TCP\n"); | ||
388 | p += ETH_GSTRING_LEN; | ||
389 | num_strings++; | ||
390 | break; | ||
391 | case UDP_V4_FLOW: | ||
392 | sprintf(p, "\tFlow Type: UDP\n"); | ||
393 | p += ETH_GSTRING_LEN; | ||
394 | num_strings++; | ||
395 | break; | ||
396 | case SCTP_V4_FLOW: | ||
397 | sprintf(p, "\tFlow Type: SCTP\n"); | ||
398 | p += ETH_GSTRING_LEN; | ||
399 | num_strings++; | ||
400 | break; | ||
401 | case AH_ESP_V4_FLOW: | ||
402 | sprintf(p, "\tFlow Type: AH ESP\n"); | ||
403 | p += ETH_GSTRING_LEN; | ||
404 | num_strings++; | ||
405 | break; | ||
406 | case ESP_V4_FLOW: | ||
407 | sprintf(p, "\tFlow Type: ESP\n"); | ||
408 | p += ETH_GSTRING_LEN; | ||
409 | num_strings++; | ||
410 | break; | ||
411 | case IP_USER_FLOW: | ||
412 | sprintf(p, "\tFlow Type: Raw IP\n"); | ||
413 | p += ETH_GSTRING_LEN; | ||
414 | num_strings++; | ||
415 | break; | ||
416 | case IPV4_FLOW: | ||
417 | sprintf(p, "\tFlow Type: IPv4\n"); | ||
418 | p += ETH_GSTRING_LEN; | ||
419 | num_strings++; | ||
420 | break; | ||
421 | default: | ||
422 | sprintf(p, "\tFlow Type: Unknown\n"); | ||
423 | p += ETH_GSTRING_LEN; | ||
424 | num_strings++; | ||
425 | goto unknown_filter; | ||
426 | }; | ||
427 | |||
428 | /* now the rest of the filters */ | ||
429 | switch (fsc->fs.flow_type) { | ||
430 | case TCP_V4_FLOW: | ||
431 | case UDP_V4_FLOW: | ||
432 | case SCTP_V4_FLOW: | ||
433 | sprintf(p, "\tSrc IP addr: 0x%x\n", | ||
434 | fsc->fs.h_u.tcp_ip4_spec.ip4src); | ||
435 | p += ETH_GSTRING_LEN; | ||
436 | num_strings++; | ||
437 | sprintf(p, "\tSrc IP mask: 0x%x\n", | ||
438 | fsc->fs.m_u.tcp_ip4_spec.ip4src); | ||
439 | p += ETH_GSTRING_LEN; | ||
440 | num_strings++; | ||
441 | sprintf(p, "\tDest IP addr: 0x%x\n", | ||
442 | fsc->fs.h_u.tcp_ip4_spec.ip4dst); | ||
443 | p += ETH_GSTRING_LEN; | ||
444 | num_strings++; | ||
445 | sprintf(p, "\tDest IP mask: 0x%x\n", | ||
446 | fsc->fs.m_u.tcp_ip4_spec.ip4dst); | ||
447 | p += ETH_GSTRING_LEN; | ||
448 | num_strings++; | ||
449 | sprintf(p, "\tSrc Port: %d, mask: 0x%x\n", | ||
450 | fsc->fs.h_u.tcp_ip4_spec.psrc, | ||
451 | fsc->fs.m_u.tcp_ip4_spec.psrc); | ||
452 | p += ETH_GSTRING_LEN; | ||
453 | num_strings++; | ||
454 | sprintf(p, "\tDest Port: %d, mask: 0x%x\n", | ||
455 | fsc->fs.h_u.tcp_ip4_spec.pdst, | ||
456 | fsc->fs.m_u.tcp_ip4_spec.pdst); | ||
457 | p += ETH_GSTRING_LEN; | ||
458 | num_strings++; | ||
459 | sprintf(p, "\tTOS: %d, mask: 0x%x\n", | ||
460 | fsc->fs.h_u.tcp_ip4_spec.tos, | ||
461 | fsc->fs.m_u.tcp_ip4_spec.tos); | ||
462 | p += ETH_GSTRING_LEN; | ||
463 | num_strings++; | ||
464 | break; | ||
465 | case AH_ESP_V4_FLOW: | ||
466 | case ESP_V4_FLOW: | ||
467 | sprintf(p, "\tSrc IP addr: 0x%x\n", | ||
468 | fsc->fs.h_u.ah_ip4_spec.ip4src); | ||
469 | p += ETH_GSTRING_LEN; | ||
470 | num_strings++; | ||
471 | sprintf(p, "\tSrc IP mask: 0x%x\n", | ||
472 | fsc->fs.m_u.ah_ip4_spec.ip4src); | ||
473 | p += ETH_GSTRING_LEN; | ||
474 | num_strings++; | ||
475 | sprintf(p, "\tDest IP addr: 0x%x\n", | ||
476 | fsc->fs.h_u.ah_ip4_spec.ip4dst); | ||
477 | p += ETH_GSTRING_LEN; | ||
478 | num_strings++; | ||
479 | sprintf(p, "\tDest IP mask: 0x%x\n", | ||
480 | fsc->fs.m_u.ah_ip4_spec.ip4dst); | ||
481 | p += ETH_GSTRING_LEN; | ||
482 | num_strings++; | ||
483 | sprintf(p, "\tSPI: %d, mask: 0x%x\n", | ||
484 | fsc->fs.h_u.ah_ip4_spec.spi, | ||
485 | fsc->fs.m_u.ah_ip4_spec.spi); | ||
486 | p += ETH_GSTRING_LEN; | ||
487 | num_strings++; | ||
488 | sprintf(p, "\tTOS: %d, mask: 0x%x\n", | ||
489 | fsc->fs.h_u.ah_ip4_spec.tos, | ||
490 | fsc->fs.m_u.ah_ip4_spec.tos); | ||
491 | p += ETH_GSTRING_LEN; | ||
492 | num_strings++; | ||
493 | break; | ||
494 | case IP_USER_FLOW: | ||
495 | sprintf(p, "\tSrc IP addr: 0x%x\n", | ||
496 | fsc->fs.h_u.raw_ip4_spec.ip4src); | ||
497 | p += ETH_GSTRING_LEN; | ||
498 | num_strings++; | ||
499 | sprintf(p, "\tSrc IP mask: 0x%x\n", | ||
500 | fsc->fs.m_u.raw_ip4_spec.ip4src); | ||
501 | p += ETH_GSTRING_LEN; | ||
502 | num_strings++; | ||
503 | sprintf(p, "\tDest IP addr: 0x%x\n", | ||
504 | fsc->fs.h_u.raw_ip4_spec.ip4dst); | ||
505 | p += ETH_GSTRING_LEN; | ||
506 | num_strings++; | ||
507 | sprintf(p, "\tDest IP mask: 0x%x\n", | ||
508 | fsc->fs.m_u.raw_ip4_spec.ip4dst); | ||
509 | p += ETH_GSTRING_LEN; | ||
510 | num_strings++; | ||
511 | break; | ||
512 | case IPV4_FLOW: | ||
513 | sprintf(p, "\tSrc IP addr: 0x%x\n", | ||
514 | fsc->fs.h_u.usr_ip4_spec.ip4src); | ||
515 | p += ETH_GSTRING_LEN; | ||
516 | num_strings++; | ||
517 | sprintf(p, "\tSrc IP mask: 0x%x\n", | ||
518 | fsc->fs.m_u.usr_ip4_spec.ip4src); | ||
519 | p += ETH_GSTRING_LEN; | ||
520 | num_strings++; | ||
521 | sprintf(p, "\tDest IP addr: 0x%x\n", | ||
522 | fsc->fs.h_u.usr_ip4_spec.ip4dst); | ||
523 | p += ETH_GSTRING_LEN; | ||
524 | num_strings++; | ||
525 | sprintf(p, "\tDest IP mask: 0x%x\n", | ||
526 | fsc->fs.m_u.usr_ip4_spec.ip4dst); | ||
527 | p += ETH_GSTRING_LEN; | ||
528 | num_strings++; | ||
529 | sprintf(p, "\tL4 bytes: 0x%x, mask: 0x%x\n", | ||
530 | fsc->fs.h_u.usr_ip4_spec.l4_4_bytes, | ||
531 | fsc->fs.m_u.usr_ip4_spec.l4_4_bytes); | ||
532 | p += ETH_GSTRING_LEN; | ||
533 | num_strings++; | ||
534 | sprintf(p, "\tTOS: %d, mask: 0x%x\n", | ||
535 | fsc->fs.h_u.usr_ip4_spec.tos, | ||
536 | fsc->fs.m_u.usr_ip4_spec.tos); | ||
537 | p += ETH_GSTRING_LEN; | ||
538 | num_strings++; | ||
539 | sprintf(p, "\tIP Version: %d, mask: 0x%x\n", | ||
540 | fsc->fs.h_u.usr_ip4_spec.ip_ver, | ||
541 | fsc->fs.m_u.usr_ip4_spec.ip_ver); | ||
542 | p += ETH_GSTRING_LEN; | ||
543 | num_strings++; | ||
544 | sprintf(p, "\tProtocol: %d, mask: 0x%x\n", | ||
545 | fsc->fs.h_u.usr_ip4_spec.proto, | ||
546 | fsc->fs.m_u.usr_ip4_spec.proto); | ||
547 | p += ETH_GSTRING_LEN; | ||
548 | num_strings++; | ||
549 | break; | ||
550 | }; | ||
551 | sprintf(p, "\tVLAN: %d, mask: 0x%x\n", | ||
552 | fsc->fs.vlan_tag, fsc->fs.vlan_tag_mask); | ||
553 | p += ETH_GSTRING_LEN; | ||
554 | num_strings++; | ||
555 | sprintf(p, "\tUser-defined: 0x%Lx\n", fsc->fs.data); | ||
556 | p += ETH_GSTRING_LEN; | ||
557 | num_strings++; | ||
558 | sprintf(p, "\tUser-defined mask: 0x%Lx\n", fsc->fs.data_mask); | ||
559 | p += ETH_GSTRING_LEN; | ||
560 | num_strings++; | ||
561 | if (fsc->fs.action == ETHTOOL_RXNTUPLE_ACTION_DROP) | ||
562 | sprintf(p, "\tAction: Drop\n"); | ||
563 | else | ||
564 | sprintf(p, "\tAction: Direct to queue %d\n", | ||
565 | fsc->fs.action); | ||
566 | p += ETH_GSTRING_LEN; | ||
567 | num_strings++; | ||
568 | unknown_filter: | ||
569 | i++; | ||
570 | } | ||
571 | copy: | ||
572 | /* indicate to userspace how many strings we actually have */ | ||
573 | gstrings.len = num_strings; | ||
574 | ret = -EFAULT; | ||
575 | if (copy_to_user(useraddr, &gstrings, sizeof(gstrings))) | ||
576 | goto out; | ||
577 | useraddr += sizeof(gstrings); | ||
578 | if (copy_to_user(useraddr, data, gstrings.len * ETH_GSTRING_LEN)) | ||
579 | goto out; | ||
580 | ret = 0; | ||
581 | |||
582 | out: | ||
583 | kfree(data); | ||
584 | return ret; | ||
585 | } | ||
586 | |||
269 | static int ethtool_get_regs(struct net_device *dev, char __user *useraddr) | 587 | static int ethtool_get_regs(struct net_device *dev, char __user *useraddr) |
270 | { | 588 | { |
271 | struct ethtool_regs regs; | 589 | struct ethtool_regs regs; |
@@ -313,6 +631,9 @@ static int ethtool_reset(struct net_device *dev, char __user *useraddr) | |||
313 | if (copy_from_user(&reset, useraddr, sizeof(reset))) | 631 | if (copy_from_user(&reset, useraddr, sizeof(reset))) |
314 | return -EFAULT; | 632 | return -EFAULT; |
315 | 633 | ||
634 | /* Clear ethtool n-tuple list */ | ||
635 | ethtool_ntuple_flush(dev); | ||
636 | |||
316 | ret = dev->ethtool_ops->reset(dev, &reset.data); | 637 | ret = dev->ethtool_ops->reset(dev, &reset.data); |
317 | if (ret) | 638 | if (ret) |
318 | return ret; | 639 | return ret; |
@@ -1112,6 +1433,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) | |||
1112 | case ETHTOOL_RESET: | 1433 | case ETHTOOL_RESET: |
1113 | rc = ethtool_reset(dev, useraddr); | 1434 | rc = ethtool_reset(dev, useraddr); |
1114 | break; | 1435 | break; |
1436 | case ETHTOOL_SRXNTUPLE: | ||
1437 | rc = ethtool_set_rx_ntuple(dev, useraddr); | ||
1438 | break; | ||
1439 | case ETHTOOL_GRXNTUPLE: | ||
1440 | rc = ethtool_get_rx_ntuple(dev, useraddr); | ||
1441 | break; | ||
1115 | default: | 1442 | default: |
1116 | rc = -EOPNOTSUPP; | 1443 | rc = -EOPNOTSUPP; |
1117 | } | 1444 | } |