diff options
author | Venkata Duvvuru <VenkatKumar.Duvvuru@Emulex.Com> | 2014-04-21 06:07:59 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-04-22 21:27:57 -0400 |
commit | 3de0b592394d17b2c41a261a6a493a521213f299 (patch) | |
tree | 34dbd689895d3c808fc25feb3c881a6f378236fe | |
parent | 862aa49164812e8da2d5b96323ed2b680f255e71 (diff) |
ethtool: Support for configurable RSS hash key
This ethtool patch primarily copies the ioctl command data structures
from/to the User space and invokes the driver hook.
Signed-off-by: Venkat Duvvuru <VenkatKumar.Duvvuru@Emulex.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/ethtool.h | 13 | ||||
-rw-r--r-- | include/uapi/linux/ethtool.h | 32 | ||||
-rw-r--r-- | net/core/ethtool.c | 221 |
3 files changed, 252 insertions, 14 deletions
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 0a114d05f68d..212f537fc686 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h | |||
@@ -154,13 +154,23 @@ static inline u32 ethtool_rxfh_indir_default(u32 index, u32 n_rx_rings) | |||
154 | * @reset: Reset (part of) the device, as specified by a bitmask of | 154 | * @reset: Reset (part of) the device, as specified by a bitmask of |
155 | * flags from &enum ethtool_reset_flags. Returns a negative | 155 | * flags from &enum ethtool_reset_flags. Returns a negative |
156 | * error code or zero. | 156 | * error code or zero. |
157 | * @get_rxfh_key_size: Get the size of the RX flow hash key. | ||
158 | * Returns zero if not supported for this specific device. | ||
157 | * @get_rxfh_indir_size: Get the size of the RX flow hash indirection table. | 159 | * @get_rxfh_indir_size: Get the size of the RX flow hash indirection table. |
158 | * Returns zero if not supported for this specific device. | 160 | * Returns zero if not supported for this specific device. |
159 | * @get_rxfh_indir: Get the contents of the RX flow hash indirection table. | 161 | * @get_rxfh_indir: Get the contents of the RX flow hash indirection table. |
160 | * Will not be called if @get_rxfh_indir_size returns zero. | 162 | * Will not be called if @get_rxfh_indir_size returns zero. |
163 | * @get_rxfh: Get the contents of the RX flow hash indirection table and hash | ||
164 | * key. | ||
165 | * Will not be called if @get_rxfh_indir_size and @get_rxfh_key_size | ||
166 | * returns zero. | ||
161 | * Returns a negative error code or zero. | 167 | * Returns a negative error code or zero. |
162 | * @set_rxfh_indir: Set the contents of the RX flow hash indirection table. | 168 | * @set_rxfh_indir: Set the contents of the RX flow hash indirection table. |
163 | * Will not be called if @get_rxfh_indir_size returns zero. | 169 | * Will not be called if @get_rxfh_indir_size returns zero. |
170 | * @set_rxfh: Set the contents of the RX flow hash indirection table and | ||
171 | * hash key. | ||
172 | * Will not be called if @get_rxfh_indir_size and @get_rxfh_key_size | ||
173 | * returns zero. | ||
164 | * Returns a negative error code or zero. | 174 | * Returns a negative error code or zero. |
165 | * @get_channels: Get number of channels. | 175 | * @get_channels: Get number of channels. |
166 | * @set_channels: Set number of channels. Returns a negative error code or | 176 | * @set_channels: Set number of channels. Returns a negative error code or |
@@ -232,7 +242,10 @@ struct ethtool_ops { | |||
232 | int (*set_rxnfc)(struct net_device *, struct ethtool_rxnfc *); | 242 | int (*set_rxnfc)(struct net_device *, struct ethtool_rxnfc *); |
233 | int (*flash_device)(struct net_device *, struct ethtool_flash *); | 243 | int (*flash_device)(struct net_device *, struct ethtool_flash *); |
234 | int (*reset)(struct net_device *, u32 *); | 244 | int (*reset)(struct net_device *, u32 *); |
245 | u32 (*get_rxfh_key_size)(struct net_device *); | ||
235 | u32 (*get_rxfh_indir_size)(struct net_device *); | 246 | u32 (*get_rxfh_indir_size)(struct net_device *); |
247 | int (*get_rxfh)(struct net_device *, u32 *, u8 *); | ||
248 | int (*set_rxfh)(struct net_device *, u32 *, u8 *); | ||
236 | int (*get_rxfh_indir)(struct net_device *, u32 *); | 249 | int (*get_rxfh_indir)(struct net_device *, u32 *); |
237 | int (*set_rxfh_indir)(struct net_device *, const u32 *); | 250 | int (*set_rxfh_indir)(struct net_device *, const u32 *); |
238 | void (*get_channels)(struct net_device *, struct ethtool_channels *); | 251 | void (*get_channels)(struct net_device *, struct ethtool_channels *); |
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index fd161e91b6d7..d47d31d6fa0e 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h | |||
@@ -847,6 +847,35 @@ struct ethtool_rxfh_indir { | |||
847 | }; | 847 | }; |
848 | 848 | ||
849 | /** | 849 | /** |
850 | * struct ethtool_rxfh - command to get/set RX flow hash indir or/and hash key. | ||
851 | * @cmd: Specific command number - %ETHTOOL_GRSSH or %ETHTOOL_SRSSH | ||
852 | * @rss_context: RSS context identifier. | ||
853 | * @indir_size: On entry, the array size of the user buffer, which may be zero. | ||
854 | * On return from %ETHTOOL_GRSSH, the array size of the hardware | ||
855 | * indirection table. | ||
856 | * @key_size: On entry, the array size of the user buffer in bytes, | ||
857 | * which may be zero. | ||
858 | * On return from %ETHTOOL_GRSSH, the size of the RSS hash key. | ||
859 | * @rsvd: Reserved for future extensions. | ||
860 | * @rss_config: RX ring/queue index for each hash value i.e., indirection table | ||
861 | * of size @indir_size followed by hash key of size @key_size. | ||
862 | * | ||
863 | * For %ETHTOOL_GRSSH, a @indir_size and key_size of zero means that only the | ||
864 | * size should be returned. For %ETHTOOL_SRSSH, a @indir_size of 0xDEADBEEF | ||
865 | * means that indir table setting is not requested and a @indir_size of zero | ||
866 | * means the indir table should be reset to default values. This last feature | ||
867 | * is not supported by the original implementations. | ||
868 | */ | ||
869 | struct ethtool_rxfh { | ||
870 | __u32 cmd; | ||
871 | __u32 rss_context; | ||
872 | __u32 indir_size; | ||
873 | __u32 key_size; | ||
874 | __u32 rsvd[2]; | ||
875 | __u32 rss_config[0]; | ||
876 | }; | ||
877 | |||
878 | /** | ||
850 | * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter | 879 | * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter |
851 | * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW | 880 | * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW |
852 | * @h_u: Flow field values to match (dependent on @flow_type) | 881 | * @h_u: Flow field values to match (dependent on @flow_type) |
@@ -1118,6 +1147,9 @@ enum ethtool_sfeatures_retval_bits { | |||
1118 | #define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ | 1147 | #define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ |
1119 | #define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ | 1148 | #define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ |
1120 | 1149 | ||
1150 | #define ETHTOOL_GRSSH 0x00000046 /* Get RX flow hash configuration */ | ||
1151 | #define ETHTOOL_SRSSH 0x00000047 /* Set RX flow hash configuration */ | ||
1152 | |||
1121 | /* compatibility with older code */ | 1153 | /* compatibility with older code */ |
1122 | #define SPARC_ETH_GSET ETHTOOL_GSET | 1154 | #define SPARC_ETH_GSET ETHTOOL_GSET |
1123 | #define SPARC_ETH_SSET ETHTOOL_SSET | 1155 | #define SPARC_ETH_SSET ETHTOOL_SSET |
diff --git a/net/core/ethtool.c b/net/core/ethtool.c index 640ba0e5831c..1d72786ef866 100644 --- a/net/core/ethtool.c +++ b/net/core/ethtool.c | |||
@@ -557,6 +557,23 @@ err_out: | |||
557 | return ret; | 557 | return ret; |
558 | } | 558 | } |
559 | 559 | ||
560 | static int ethtool_copy_validate_indir(u32 *indir, void __user *useraddr, | ||
561 | struct ethtool_rxnfc *rx_rings, | ||
562 | u32 size) | ||
563 | { | ||
564 | int ret = 0, i; | ||
565 | |||
566 | if (copy_from_user(indir, useraddr, size * sizeof(indir[0]))) | ||
567 | ret = -EFAULT; | ||
568 | |||
569 | /* Validate ring indices */ | ||
570 | for (i = 0; i < size; i++) { | ||
571 | if (indir[i] >= rx_rings->data) | ||
572 | ret = -EINVAL; | ||
573 | } | ||
574 | return ret; | ||
575 | } | ||
576 | |||
560 | static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev, | 577 | static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev, |
561 | void __user *useraddr) | 578 | void __user *useraddr) |
562 | { | 579 | { |
@@ -613,6 +630,7 @@ static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev, | |||
613 | u32 *indir; | 630 | u32 *indir; |
614 | const struct ethtool_ops *ops = dev->ethtool_ops; | 631 | const struct ethtool_ops *ops = dev->ethtool_ops; |
615 | int ret; | 632 | int ret; |
633 | u32 ringidx_offset = offsetof(struct ethtool_rxfh_indir, ring_index[0]); | ||
616 | 634 | ||
617 | if (!ops->get_rxfh_indir_size || !ops->set_rxfh_indir || | 635 | if (!ops->get_rxfh_indir_size || !ops->set_rxfh_indir || |
618 | !ops->get_rxnfc) | 636 | !ops->get_rxnfc) |
@@ -643,28 +661,196 @@ static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev, | |||
643 | for (i = 0; i < dev_size; i++) | 661 | for (i = 0; i < dev_size; i++) |
644 | indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); | 662 | indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); |
645 | } else { | 663 | } else { |
646 | if (copy_from_user(indir, | 664 | ret = ethtool_copy_validate_indir(indir, |
647 | useraddr + | 665 | useraddr + ringidx_offset, |
648 | offsetof(struct ethtool_rxfh_indir, | 666 | &rx_rings, |
649 | ring_index[0]), | 667 | dev_size); |
650 | dev_size * sizeof(indir[0]))) { | 668 | if (ret) |
669 | goto out; | ||
670 | } | ||
671 | |||
672 | ret = ops->set_rxfh_indir(dev, indir); | ||
673 | |||
674 | out: | ||
675 | kfree(indir); | ||
676 | return ret; | ||
677 | } | ||
678 | |||
679 | static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev, | ||
680 | void __user *useraddr) | ||
681 | { | ||
682 | int ret; | ||
683 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
684 | u32 user_indir_size = 0, user_key_size = 0; | ||
685 | u32 dev_indir_size = 0, dev_key_size = 0; | ||
686 | u32 total_size; | ||
687 | u32 indir_offset, indir_bytes; | ||
688 | u32 key_offset; | ||
689 | u32 *indir = NULL; | ||
690 | u8 *hkey = NULL; | ||
691 | u8 *rss_config; | ||
692 | |||
693 | if (!(dev->ethtool_ops->get_rxfh_indir_size || | ||
694 | dev->ethtool_ops->get_rxfh_key_size) || | ||
695 | !dev->ethtool_ops->get_rxfh) | ||
696 | return -EOPNOTSUPP; | ||
697 | |||
698 | if (ops->get_rxfh_indir_size) | ||
699 | dev_indir_size = ops->get_rxfh_indir_size(dev); | ||
700 | |||
701 | indir_offset = offsetof(struct ethtool_rxfh, indir_size); | ||
702 | |||
703 | if (copy_from_user(&user_indir_size, | ||
704 | useraddr + indir_offset, | ||
705 | sizeof(user_indir_size))) | ||
706 | return -EFAULT; | ||
707 | |||
708 | if (copy_to_user(useraddr + indir_offset, | ||
709 | &dev_indir_size, sizeof(dev_indir_size))) | ||
710 | return -EFAULT; | ||
711 | |||
712 | if (ops->get_rxfh_key_size) | ||
713 | dev_key_size = ops->get_rxfh_key_size(dev); | ||
714 | |||
715 | if ((dev_key_size + dev_indir_size) == 0) | ||
716 | return -EOPNOTSUPP; | ||
717 | |||
718 | key_offset = offsetof(struct ethtool_rxfh, key_size); | ||
719 | |||
720 | if (copy_from_user(&user_key_size, | ||
721 | useraddr + key_offset, | ||
722 | sizeof(user_key_size))) | ||
723 | return -EFAULT; | ||
724 | |||
725 | if (copy_to_user(useraddr + key_offset, | ||
726 | &dev_key_size, sizeof(dev_key_size))) | ||
727 | return -EFAULT; | ||
728 | |||
729 | /* If the user buffer size is 0, this is just a query for the | ||
730 | * device table size and key size. Otherwise, if the User size is | ||
731 | * not equal to device table size or key size it's an error. | ||
732 | */ | ||
733 | if (!user_indir_size && !user_key_size) | ||
734 | return 0; | ||
735 | |||
736 | if ((user_indir_size && (user_indir_size != dev_indir_size)) || | ||
737 | (user_key_size && (user_key_size != dev_key_size))) | ||
738 | return -EINVAL; | ||
739 | |||
740 | indir_bytes = user_indir_size * sizeof(indir[0]); | ||
741 | total_size = indir_bytes + user_key_size; | ||
742 | rss_config = kzalloc(total_size, GFP_USER); | ||
743 | if (!rss_config) | ||
744 | return -ENOMEM; | ||
745 | |||
746 | if (user_indir_size) | ||
747 | indir = (u32 *)rss_config; | ||
748 | |||
749 | if (user_key_size) | ||
750 | hkey = rss_config + indir_bytes; | ||
751 | |||
752 | ret = dev->ethtool_ops->get_rxfh(dev, indir, hkey); | ||
753 | if (!ret) { | ||
754 | if (copy_to_user(useraddr + | ||
755 | offsetof(struct ethtool_rxfh, rss_config[0]), | ||
756 | rss_config, total_size)) | ||
651 | ret = -EFAULT; | 757 | ret = -EFAULT; |
758 | } | ||
759 | |||
760 | kfree(rss_config); | ||
761 | |||
762 | return ret; | ||
763 | } | ||
764 | |||
765 | static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, | ||
766 | void __user *useraddr) | ||
767 | { | ||
768 | int ret; | ||
769 | const struct ethtool_ops *ops = dev->ethtool_ops; | ||
770 | struct ethtool_rxnfc rx_rings; | ||
771 | u32 user_indir_size = 0, dev_indir_size = 0, i; | ||
772 | u32 user_key_size = 0, dev_key_size = 0; | ||
773 | u32 *indir = NULL, indir_bytes = 0; | ||
774 | u8 *hkey = NULL; | ||
775 | u8 *rss_config; | ||
776 | u32 indir_offset, key_offset; | ||
777 | u32 rss_cfg_offset = offsetof(struct ethtool_rxfh, rss_config[0]); | ||
778 | |||
779 | if (!(ops->get_rxfh_indir_size || ops->get_rxfh_key_size) || | ||
780 | !ops->get_rxnfc || !ops->set_rxfh) | ||
781 | return -EOPNOTSUPP; | ||
782 | |||
783 | if (ops->get_rxfh_indir_size) | ||
784 | dev_indir_size = ops->get_rxfh_indir_size(dev); | ||
785 | |||
786 | indir_offset = offsetof(struct ethtool_rxfh, indir_size); | ||
787 | if (copy_from_user(&user_indir_size, | ||
788 | useraddr + indir_offset, | ||
789 | sizeof(user_indir_size))) | ||
790 | return -EFAULT; | ||
791 | |||
792 | if (ops->get_rxfh_key_size) | ||
793 | dev_key_size = dev->ethtool_ops->get_rxfh_key_size(dev); | ||
794 | |||
795 | if ((dev_key_size + dev_indir_size) == 0) | ||
796 | return -EOPNOTSUPP; | ||
797 | |||
798 | key_offset = offsetof(struct ethtool_rxfh, key_size); | ||
799 | if (copy_from_user(&user_key_size, | ||
800 | useraddr + key_offset, | ||
801 | sizeof(user_key_size))) | ||
802 | return -EFAULT; | ||
803 | |||
804 | /* If either indir or hash key is valid, proceed further. | ||
805 | */ | ||
806 | if ((user_indir_size && ((user_indir_size != 0xDEADBEEF) && | ||
807 | user_indir_size != dev_indir_size)) || | ||
808 | (user_key_size && (user_key_size != dev_key_size))) | ||
809 | return -EINVAL; | ||
810 | |||
811 | if (user_indir_size != 0xDEADBEEF) | ||
812 | indir_bytes = dev_indir_size * sizeof(indir[0]); | ||
813 | |||
814 | rss_config = kzalloc(indir_bytes + user_key_size, GFP_USER); | ||
815 | if (!rss_config) | ||
816 | return -ENOMEM; | ||
817 | |||
818 | rx_rings.cmd = ETHTOOL_GRXRINGS; | ||
819 | ret = ops->get_rxnfc(dev, &rx_rings, NULL); | ||
820 | if (ret) | ||
821 | goto out; | ||
822 | |||
823 | /* user_indir_size == 0 means reset the indir table to default. | ||
824 | * user_indir_size == 0xDEADBEEF means indir setting is not requested. | ||
825 | */ | ||
826 | if (user_indir_size && user_indir_size != 0xDEADBEEF) { | ||
827 | indir = (u32 *)rss_config; | ||
828 | ret = ethtool_copy_validate_indir(indir, | ||
829 | useraddr + rss_cfg_offset, | ||
830 | &rx_rings, | ||
831 | user_indir_size); | ||
832 | if (ret) | ||
652 | goto out; | 833 | goto out; |
653 | } | 834 | } else if (user_indir_size == 0) { |
835 | indir = (u32 *)rss_config; | ||
836 | for (i = 0; i < dev_indir_size; i++) | ||
837 | indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); | ||
838 | } | ||
654 | 839 | ||
655 | /* Validate ring indices */ | 840 | if (user_key_size) { |
656 | for (i = 0; i < dev_size; i++) { | 841 | hkey = rss_config + indir_bytes; |
657 | if (indir[i] >= rx_rings.data) { | 842 | if (copy_from_user(hkey, |
658 | ret = -EINVAL; | 843 | useraddr + rss_cfg_offset + indir_bytes, |
659 | goto out; | 844 | user_key_size)) { |
660 | } | 845 | ret = -EFAULT; |
846 | goto out; | ||
661 | } | 847 | } |
662 | } | 848 | } |
663 | 849 | ||
664 | ret = ops->set_rxfh_indir(dev, indir); | 850 | ret = ops->set_rxfh(dev, indir, hkey); |
665 | 851 | ||
666 | out: | 852 | out: |
667 | kfree(indir); | 853 | kfree(rss_config); |
668 | return ret; | 854 | return ret; |
669 | } | 855 | } |
670 | 856 | ||
@@ -1491,6 +1677,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) | |||
1491 | case ETHTOOL_GRXCLSRULE: | 1677 | case ETHTOOL_GRXCLSRULE: |
1492 | case ETHTOOL_GRXCLSRLALL: | 1678 | case ETHTOOL_GRXCLSRLALL: |
1493 | case ETHTOOL_GRXFHINDIR: | 1679 | case ETHTOOL_GRXFHINDIR: |
1680 | case ETHTOOL_GRSSH: | ||
1494 | case ETHTOOL_GFEATURES: | 1681 | case ETHTOOL_GFEATURES: |
1495 | case ETHTOOL_GCHANNELS: | 1682 | case ETHTOOL_GCHANNELS: |
1496 | case ETHTOOL_GET_TS_INFO: | 1683 | case ETHTOOL_GET_TS_INFO: |
@@ -1628,6 +1815,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) | |||
1628 | case ETHTOOL_SRXFHINDIR: | 1815 | case ETHTOOL_SRXFHINDIR: |
1629 | rc = ethtool_set_rxfh_indir(dev, useraddr); | 1816 | rc = ethtool_set_rxfh_indir(dev, useraddr); |
1630 | break; | 1817 | break; |
1818 | case ETHTOOL_GRSSH: | ||
1819 | rc = ethtool_get_rxfh(dev, useraddr); | ||
1820 | break; | ||
1821 | case ETHTOOL_SRSSH: | ||
1822 | rc = ethtool_set_rxfh(dev, useraddr); | ||
1823 | break; | ||
1631 | case ETHTOOL_GFEATURES: | 1824 | case ETHTOOL_GFEATURES: |
1632 | rc = ethtool_get_features(dev, useraddr); | 1825 | rc = ethtool_get_features(dev, useraddr); |
1633 | break; | 1826 | break; |