/* * net/core/ethtool.c - Ethtool ioctl handler * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> * * This file is where we call all the ethtool_ops commands to get * the information ethtool needs. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include <linux/module.h>#include <linux/types.h>#include <linux/capability.h>#include <linux/errno.h>#include <linux/ethtool.h>#include <linux/netdevice.h>#include <linux/bitops.h>#include <linux/uaccess.h>#include <linux/vmalloc.h>#include <linux/slab.h>#include <linux/rtnetlink.h>#include <linux/sched.h> /* * Some useful ethtool_ops methods that're device independent. * If we find that all drivers want to do the same thing here, * we can turn these into dev_() function calls. */ u32 ethtool_op_get_link(struct net_device *dev){ return netif_carrier_ok(dev) ? 1 : 0;}EXPORT_SYMBOL(ethtool_op_get_link); u32 ethtool_op_get_tx_csum(struct net_device *dev){ return (dev->features & NETIF_F_ALL_CSUM) != 0;}EXPORT_SYMBOL(ethtool_op_get_tx_csum); int ethtool_op_set_tx_csum(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_IP_CSUM; else dev->features &= ~NETIF_F_IP_CSUM; return 0;}EXPORT_SYMBOL(ethtool_op_set_tx_csum); int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_HW_CSUM; else dev->features &= ~NETIF_F_HW_CSUM; return 0;}EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum); int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; else dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); return 0;}EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum); u32 ethtool_op_get_sg(struct net_device *dev){ return (dev->features & NETIF_F_SG) != 0;}EXPORT_SYMBOL(ethtool_op_get_sg); int ethtool_op_set_sg(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_SG; else dev->features &= ~NETIF_F_SG; return 0;}EXPORT_SYMBOL(ethtool_op_set_sg); u32 ethtool_op_get_tso(struct net_device *dev){ return (dev->features & NETIF_F_TSO) != 0;}EXPORT_SYMBOL(ethtool_op_get_tso); int ethtool_op_set_tso(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_TSO; else dev->features &= ~NETIF_F_TSO; return 0;}EXPORT_SYMBOL(ethtool_op_set_tso); u32 ethtool_op_get_ufo(struct net_device *dev){ return (dev->features & NETIF_F_UFO) != 0;}EXPORT_SYMBOL(ethtool_op_get_ufo); int ethtool_op_set_ufo(struct net_device *dev, u32 data){ if (data) dev->features |= NETIF_F_UFO; else dev->features &= ~NETIF_F_UFO; return 0;}EXPORT_SYMBOL(ethtool_op_set_ufo); /* the following list of flags are the same as their associated * NETIF_F_xxx values in include/linux/netdevice.h */static const u32 flags_dup_features = (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH); u32 ethtool_op_get_flags(struct net_device *dev){ /* in the future, this function will probably contain additional * handling for flags which are not so easily handled * by a simple masking operation */ return dev->features & flags_dup_features;}EXPORT_SYMBOL(ethtool_op_get_flags); /* Check if device can enable (or disable) particular feature coded in "data" * argument. Flags "supported" describe features that can be toggled by device. * If feature can not be toggled, it state (enabled or disabled) must match * hardcoded device features state, otherwise flags are marked as invalid. */bool ethtool_invalid_flags(struct net_device *dev, u32 data, u32 supported){ u32 features = dev->features & flags_dup_features; /* "data" can contain only flags_dup_features bits, * see __ethtool_set_flags */ return (features & ~supported) != (data & ~supported);}EXPORT_SYMBOL(ethtool_invalid_flags); int ethtool_op_set_flags(struct net_device *dev, u32 data, u32 supported){ if (ethtool_invalid_flags(dev, data, supported)) return -EINVAL; dev->features = ((dev->features & ~flags_dup_features) | (data & flags_dup_features)); return 0;}EXPORT_SYMBOL(ethtool_op_set_flags); /* Handlers for each ethtool command */ #define ETHTOOL_DEV_FEATURE_WORDS 1 static void ethtool_get_features_compat(struct net_device *dev, struct ethtool_get_features_block *features){ if (!dev->ethtool_ops) return; /* getting RX checksum */ if (dev->ethtool_ops->get_rx_csum) if (dev->ethtool_ops->get_rx_csum(dev)) features[0].active |= NETIF_F_RXCSUM; /* mark legacy-changeable features */ if (dev->ethtool_ops->set_sg) features[0].available |= NETIF_F_SG; if (dev->ethtool_ops->set_tx_csum) features[0].available |= NETIF_F_ALL_CSUM; if (dev->ethtool_ops->set_tso) features[0].available |= NETIF_F_ALL_TSO; if (dev->ethtool_ops->set_rx_csum) features[0].available |= NETIF_F_RXCSUM; if (dev->ethtool_ops->set_flags) features[0].available |= flags_dup_features;} static int ethtool_set_feature_compat(struct net_device *dev, int (*legacy_set)(struct net_device *, u32), struct ethtool_set_features_block *features, u32 mask){ u32 do_set; if (!legacy_set) return 0; if (!(features[0].valid & mask)) return 0; features[0].valid &= ~mask; do_set = !!(features[0].requested & mask); if (legacy_set(dev, do_set) < 0) netdev_info(dev, "Legacy feature change (%s) failed for 0x%08x\n", do_set ? "set" : "clear", mask); return 1;} static int ethtool_set_flags_compat(struct net_device *dev, int (*legacy_set)(struct net_device *, u32), struct ethtool_set_features_block *features, u32 mask){ u32 value; if (!legacy_set) return 0; if (!(features[0].valid & mask)) return 0; value = dev->features & ~features[0].valid; value |= features[0].requested; features[0].valid &= ~mask; if (legacy_set(dev, value & mask) < 0) netdev_info(dev, "Legacy flags change failed\n"); return 1;} static int ethtool_set_features_compat(struct net_device *dev, struct ethtool_set_features_block *features){ int compat; if (!dev->ethtool_ops) return 0; compat = ethtool_set_feature_compat(dev, dev->ethtool_ops->set_sg, features, NETIF_F_SG); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tx_csum, features, NETIF_F_ALL_CSUM); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tso, features, NETIF_F_ALL_TSO); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum, features, NETIF_F_RXCSUM); compat |= ethtool_set_flags_compat(dev, dev->ethtool_ops->set_flags, features, flags_dup_features); return compat;} static int ethtool_get_features(struct net_device *dev, void __user *useraddr){ struct ethtool_gfeatures cmd = { .cmd = ETHTOOL_GFEATURES, .size = ETHTOOL_DEV_FEATURE_WORDS, }; struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS] = { { .available = dev->hw_features, .requested = dev->wanted_features, .active = dev->features, .never_changed = NETIF_F_NEVER_CHANGE, }, }; u32 __user *sizeaddr; u32 copy_size; ethtool_get_features_compat(dev, features); sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size); if (get_user(copy_size, sizeaddr)) return -EFAULT; if (copy_size > ETHTOOL_DEV_FEATURE_WORDS) copy_size = ETHTOOL_DEV_FEATURE_WORDS; if (copy_to_user(useraddr, &cmd, sizeof(cmd))) return -EFAULT; useraddr += sizeof(cmd); if (copy_to_user(useraddr, features, copy_size * sizeof(*features))) return -EFAULT; return 0;} static int ethtool_set_features(struct net_device *dev, void __user *useraddr){ struct ethtool_sfeatures cmd; struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; int ret = 0; if (copy_from_user(&cmd, useraddr, sizeof(cmd))) return -EFAULT; useraddr += sizeof(cmd); if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS) return -EINVAL; if (copy_from_user(features, useraddr, sizeof(features))) return -EFAULT; if (features[0].valid & ~NETIF_F_ETHTOOL_BITS) return -EINVAL; if (ethtool_set_features_compat(dev, features)) ret |= ETHTOOL_F_COMPAT; if (features[0].valid & ~dev->hw_features) { features[0].valid &= dev->hw_features; ret |= ETHTOOL_F_UNSUPPORTED; } dev->wanted_features &= ~features[0].valid; dev->wanted_features |= features[0].valid & features[0].requested; __netdev_update_features(dev); if ((dev->wanted_features ^ dev->features) & features[0].valid) ret |= ETHTOOL_F_WISH; return ret;} static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GSTRING_LEN] = { /* NETIF_F_SG */ "tx-scatter-gather", /* NETIF_F_IP_CSUM */ "tx-checksum-ipv4", /* NETIF_F_NO_CSUM */ "tx-checksum-unneeded", /* NETIF_F_HW_CSUM */ "tx-checksum-ip-generic", /* NETIF_F_IPV6_CSUM */ "tx-checksum-ipv6", /* NETIF_F_HIGHDMA */ "highdma", /* NETIF_F_FRAGLIST */ "tx-scatter-gather-fraglist", /* NETIF_F_HW_VLAN_TX */ "tx-vlan-hw-insert", /* NETIF_F_HW_VLAN_RX */ "rx-vlan-hw-parse", /* NETIF_F_HW_VLAN_FILTER */ "rx-vlan-filter", /* NETIF_F_VLAN_CHALLENGED */ "vlan-challenged", /* NETIF_F_GSO */ "tx-generic-segmentation", /* NETIF_F_LLTX */ "tx-lockless", /* NETIF_F_NETNS_LOCAL */ "netns-local", /* NETIF_F_GRO */ "rx-gro", /* NETIF_F_LRO */ "rx-lro", /* NETIF_F_TSO */ "tx-tcp-segmentation", /* NETIF_F_UFO */ "tx-udp-fragmentation", /* NETIF_F_GSO_ROBUST */ "tx-gso-robust", /* NETIF_F_TSO_ECN */ "tx-tcp-ecn-segmentation", /* NETIF_F_TSO6 */ "tx-tcp6-segmentation", /* NETIF_F_FSO */ "tx-fcoe-segmentation", "", "", /* NETIF_F_FCOE_CRC */ "tx-checksum-fcoe-crc", /* NETIF_F_SCTP_CSUM */ "tx-checksum-sctp", /* NETIF_F_FCOE_MTU */ "fcoe-mtu", /* NETIF_F_NTUPLE */ "rx-ntuple-filter", /* NETIF_F_RXHASH */ "rx-hashing", /* NETIF_F_RXCSUM */ "rx-checksum", /* NETIF_F_NOCACHE_COPY */ "tx-nocache-copy", /* NETIF_F_LOOPBACK */ "loopback",}; static int __ethtool_get_sset_count(struct net_device *dev, int sset){ const struct ethtool_ops *ops = dev->ethtool_ops; if (sset == ETH_SS_FEATURES) return ARRAY_SIZE(netdev_features_strings); if (ops && ops->get_sset_count && ops->get_strings) return ops->get_sset_count(dev, sset); else return -EOPNOTSUPP;} static void __ethtool_get_strings(struct net_device *dev, u32 stringset, u8 *data){ const struct ethtool_ops *ops = dev->ethtool_ops; if (stringset == ETH_SS_FEATURES) memcpy(data, netdev_features_strings, sizeof(netdev_features_strings)); else /* ops->get_strings is valid because checked earlier */ ops->get_strings(dev, stringset, data);} static u32 ethtool_get_feature_mask(u32 eth_cmd){ /* feature masks of legacy discrete ethtool ops */ switch (eth_cmd) { case ETHTOOL_GTXCSUM: case ETHTOOL_STXCSUM: return NETIF_F_ALL_CSUM | NETIF_F_SCTP_CSUM; case ETHTOOL_GRXCSUM: case ETHTOOL_SRXCSUM: return NETIF_F_RXCSUM; case ETHTOOL_GSG: case ETHTOOL_SSG: return NETIF_F_SG; case ETHTOOL_GTSO: case ETHTOOL_STSO: return NETIF_F_ALL_TSO; case ETHTOOL_GUFO: case ETHTOOL_SUFO: return NETIF_F_UFO; case ETHTOOL_GGSO: case ETHTOOL_SGSO: return NETIF_F_GSO; case ETHTOOL_GGRO: case ETHTOOL_SGRO: return NETIF_F_GRO; default: BUG(); }} static void *__ethtool_get_one_feature_actor(struct net_device *dev, u32 ethcmd){ const struct ethtool_ops *ops = dev->ethtool_ops; if (!ops) return NULL; switch (ethcmd) { case ETHTOOL_GTXCSUM: return ops->get_tx_csum; case ETHTOOL_GRXCSUM: return ops->get_rx_csum; case ETHTOOL_SSG: return ops->get_sg; case ETHTOOL_STSO: return ops->get_tso; case ETHTOOL_SUFO: return ops->get_ufo; default: return NULL; }} static u32 __ethtool_get_rx_csum_oldbug(struct net_device *dev){ return !!(dev->features & NETIF_F_ALL_CSUM);} static int ethtool_get_one_feature(struct net_device *dev, char __user *useraddr, u32 ethcmd){ u32 mask = ethtool_get_feature_mask(ethcmd); struct ethtool_value edata = { .cmd = ethcmd, .data = !!(dev->features & mask), }; /* compatibility with discrete get_ ops */ if (!(dev->hw_features & mask)) { u32 (*actor)(struct net_device *); actor = __ethtool_get_one_feature_actor(dev, ethcmd); /* bug compatibility with old get_rx_csum */ if (ethcmd == ETHTOOL_GRXCSUM && !actor) actor = __ethtool_get_rx_csum_oldbug; if (actor) edata.data = actor(dev); } if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0;} static int __ethtool_set_tx_csum(struct net_device *dev, u32 data);static int __ethtool_set_rx_csum(struct net_device *dev, u32 data);static int __ethtool_set_sg(struct net_device *dev, u32 data);static int __ethtool_set_tso(struct net_device *dev, u32 data);static int __ethtool_set_ufo(struct net_device *dev, u32 data); static int ethtool_set_one_feature(struct net_device *dev, void __user *useraddr, u32 ethcmd){Lines 1–500 of 1869. Load more
/* * net/core/ethtool.c - Ethtool ioctl handler * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> * * This file is where we call all the ethtool_ops commands to get * the information ethtool needs. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include <linux/module.h> #include <linux/types.h> #include <linux/capability.h> #include <linux/errno.h> #include <linux/ethtool.h> #include <linux/netdevice.h> #include <linux/bitops.h> #include <linux/uaccess.h> #include <linux/vmalloc.h> #include <linux/slab.h> #include <linux/rtnetlink.h> #include <linux/sched.h> /* * Some useful ethtool_ops methods that're device independent. * If we find that all drivers want to do the same thing here, * we can turn these into dev_() function calls. */ u32 ethtool_op_get_link(struct net_device *dev) { return netif_carrier_ok(dev) ? 1 : 0; } EXPORT_SYMBOL(ethtool_op_get_link); u32 ethtool_op_get_tx_csum(struct net_device *dev) { return (dev->features & NETIF_F_ALL_CSUM) != 0; } EXPORT_SYMBOL(ethtool_op_get_tx_csum); int ethtool_op_set_tx_csum(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_IP_CSUM; else dev->features &= ~NETIF_F_IP_CSUM; return 0; } EXPORT_SYMBOL(ethtool_op_set_tx_csum); int ethtool_op_set_tx_hw_csum(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_HW_CSUM; else dev->features &= ~NETIF_F_HW_CSUM; return 0; } EXPORT_SYMBOL(ethtool_op_set_tx_hw_csum); int ethtool_op_set_tx_ipv6_csum(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; else dev->features &= ~(NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM); return 0; } EXPORT_SYMBOL(ethtool_op_set_tx_ipv6_csum); u32 ethtool_op_get_sg(struct net_device *dev) { return (dev->features & NETIF_F_SG) != 0; } EXPORT_SYMBOL(ethtool_op_get_sg); int ethtool_op_set_sg(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_SG; else dev->features &= ~NETIF_F_SG; return 0; } EXPORT_SYMBOL(ethtool_op_set_sg); u32 ethtool_op_get_tso(struct net_device *dev) { return (dev->features & NETIF_F_TSO) != 0; } EXPORT_SYMBOL(ethtool_op_get_tso); int ethtool_op_set_tso(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_TSO; else dev->features &= ~NETIF_F_TSO; return 0; } EXPORT_SYMBOL(ethtool_op_set_tso); u32 ethtool_op_get_ufo(struct net_device *dev) { return (dev->features & NETIF_F_UFO) != 0; } EXPORT_SYMBOL(ethtool_op_get_ufo); int ethtool_op_set_ufo(struct net_device *dev, u32 data) { if (data) dev->features |= NETIF_F_UFO; else dev->features &= ~NETIF_F_UFO; return 0; } EXPORT_SYMBOL(ethtool_op_set_ufo); /* the following list of flags are the same as their associated * NETIF_F_xxx values in include/linux/netdevice.h */ static const u32 flags_dup_features = (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH); u32 ethtool_op_get_flags(struct net_device *dev) { /* in the future, this function will probably contain additional * handling for flags which are not so easily handled * by a simple masking operation */ return dev->features & flags_dup_features; } EXPORT_SYMBOL(ethtool_op_get_flags); /* Check if device can enable (or disable) particular feature coded in "data" * argument. Flags "supported" describe features that can be toggled by device. * If feature can not be toggled, it state (enabled or disabled) must match * hardcoded device features state, otherwise flags are marked as invalid. */ bool ethtool_invalid_flags(struct net_device *dev, u32 data, u32 supported) { u32 features = dev->features & flags_dup_features; /* "data" can contain only flags_dup_features bits, * see __ethtool_set_flags */ return (features & ~supported) != (data & ~supported); } EXPORT_SYMBOL(ethtool_invalid_flags); int ethtool_op_set_flags(struct net_device *dev, u32 data, u32 supported) { if (ethtool_invalid_flags(dev, data, supported)) return -EINVAL; dev->features = ((dev->features & ~flags_dup_features) | (data & flags_dup_features)); return 0; } EXPORT_SYMBOL(ethtool_op_set_flags); /* Handlers for each ethtool command */ #define ETHTOOL_DEV_FEATURE_WORDS 1 static void ethtool_get_features_compat(struct net_device *dev, struct ethtool_get_features_block *features) { if (!dev->ethtool_ops) return; /* getting RX checksum */ if (dev->ethtool_ops->get_rx_csum) if (dev->ethtool_ops->get_rx_csum(dev)) features[0].active |= NETIF_F_RXCSUM; /* mark legacy-changeable features */ if (dev->ethtool_ops->set_sg) features[0].available |= NETIF_F_SG; if (dev->ethtool_ops->set_tx_csum) features[0].available |= NETIF_F_ALL_CSUM; if (dev->ethtool_ops->set_tso) features[0].available |= NETIF_F_ALL_TSO; if (dev->ethtool_ops->set_rx_csum) features[0].available |= NETIF_F_RXCSUM; if (dev->ethtool_ops->set_flags) features[0].available |= flags_dup_features; } static int ethtool_set_feature_compat(struct net_device *dev, int (*legacy_set)(struct net_device *, u32), struct ethtool_set_features_block *features, u32 mask) { u32 do_set; if (!legacy_set) return 0; if (!(features[0].valid & mask)) return 0; features[0].valid &= ~mask; do_set = !!(features[0].requested & mask); if (legacy_set(dev, do_set) < 0) netdev_info(dev, "Legacy feature change (%s) failed for 0x%08x\n", do_set ? "set" : "clear", mask); return 1; } static int ethtool_set_flags_compat(struct net_device *dev, int (*legacy_set)(struct net_device *, u32), struct ethtool_set_features_block *features, u32 mask) { u32 value; if (!legacy_set) return 0; if (!(features[0].valid & mask)) return 0; value = dev->features & ~features[0].valid; value |= features[0].requested; features[0].valid &= ~mask; if (legacy_set(dev, value & mask) < 0) netdev_info(dev, "Legacy flags change failed\n"); return 1; } static int ethtool_set_features_compat(struct net_device *dev, struct ethtool_set_features_block *features) { int compat; if (!dev->ethtool_ops) return 0; compat = ethtool_set_feature_compat(dev, dev->ethtool_ops->set_sg, features, NETIF_F_SG); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tx_csum, features, NETIF_F_ALL_CSUM); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_tso, features, NETIF_F_ALL_TSO); compat |= ethtool_set_feature_compat(dev, dev->ethtool_ops->set_rx_csum, features, NETIF_F_RXCSUM); compat |= ethtool_set_flags_compat(dev, dev->ethtool_ops->set_flags, features, flags_dup_features); return compat; } static int ethtool_get_features(struct net_device *dev, void __user *useraddr) { struct ethtool_gfeatures cmd = { .cmd = ETHTOOL_GFEATURES, .size = ETHTOOL_DEV_FEATURE_WORDS, }; struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS] = { { .available = dev->hw_features, .requested = dev->wanted_features, .active = dev->features, .never_changed = NETIF_F_NEVER_CHANGE, }, }; u32 __user *sizeaddr; u32 copy_size; ethtool_get_features_compat(dev, features); sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size); if (get_user(copy_size, sizeaddr)) return -EFAULT; if (copy_size > ETHTOOL_DEV_FEATURE_WORDS) copy_size = ETHTOOL_DEV_FEATURE_WORDS; if (copy_to_user(useraddr, &cmd, sizeof(cmd))) return -EFAULT; useraddr += sizeof(cmd); if (copy_to_user(useraddr, features, copy_size * sizeof(*features))) return -EFAULT; return 0; } static int ethtool_set_features(struct net_device *dev, void __user *useraddr) { struct ethtool_sfeatures cmd; struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; int ret = 0; if (copy_from_user(&cmd, useraddr, sizeof(cmd))) return -EFAULT; useraddr += sizeof(cmd); if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS) return -EINVAL; if (copy_from_user(features, useraddr, sizeof(features))) return -EFAULT; if (features[0].valid & ~NETIF_F_ETHTOOL_BITS) return -EINVAL; if (ethtool_set_features_compat(dev, features)) ret |= ETHTOOL_F_COMPAT; if (features[0].valid & ~dev->hw_features) { features[0].valid &= dev->hw_features; ret |= ETHTOOL_F_UNSUPPORTED; } dev->wanted_features &= ~features[0].valid; dev->wanted_features |= features[0].valid & features[0].requested; __netdev_update_features(dev); if ((dev->wanted_features ^ dev->features) & features[0].valid) ret |= ETHTOOL_F_WISH; return ret; } static const char netdev_features_strings[ETHTOOL_DEV_FEATURE_WORDS * 32][ETH_GSTRING_LEN] = { /* NETIF_F_SG */ "tx-scatter-gather", /* NETIF_F_IP_CSUM */ "tx-checksum-ipv4", /* NETIF_F_NO_CSUM */ "tx-checksum-unneeded", /* NETIF_F_HW_CSUM */ "tx-checksum-ip-generic", /* NETIF_F_IPV6_CSUM */ "tx-checksum-ipv6", /* NETIF_F_HIGHDMA */ "highdma", /* NETIF_F_FRAGLIST */ "tx-scatter-gather-fraglist", /* NETIF_F_HW_VLAN_TX */ "tx-vlan-hw-insert", /* NETIF_F_HW_VLAN_RX */ "rx-vlan-hw-parse", /* NETIF_F_HW_VLAN_FILTER */ "rx-vlan-filter", /* NETIF_F_VLAN_CHALLENGED */ "vlan-challenged", /* NETIF_F_GSO */ "tx-generic-segmentation", /* NETIF_F_LLTX */ "tx-lockless", /* NETIF_F_NETNS_LOCAL */ "netns-local", /* NETIF_F_GRO */ "rx-gro", /* NETIF_F_LRO */ "rx-lro", /* NETIF_F_TSO */ "tx-tcp-segmentation", /* NETIF_F_UFO */ "tx-udp-fragmentation", /* NETIF_F_GSO_ROBUST */ "tx-gso-robust", /* NETIF_F_TSO_ECN */ "tx-tcp-ecn-segmentation", /* NETIF_F_TSO6 */ "tx-tcp6-segmentation", /* NETIF_F_FSO */ "tx-fcoe-segmentation", "", "", /* NETIF_F_FCOE_CRC */ "tx-checksum-fcoe-crc", /* NETIF_F_SCTP_CSUM */ "tx-checksum-sctp", /* NETIF_F_FCOE_MTU */ "fcoe-mtu", /* NETIF_F_NTUPLE */ "rx-ntuple-filter", /* NETIF_F_RXHASH */ "rx-hashing", /* NETIF_F_RXCSUM */ "rx-checksum", /* NETIF_F_NOCACHE_COPY */ "tx-nocache-copy", /* NETIF_F_LOOPBACK */ "loopback", }; static int __ethtool_get_sset_count(struct net_device *dev, int sset) { const struct ethtool_ops *ops = dev->ethtool_ops; if (sset == ETH_SS_FEATURES) return ARRAY_SIZE(netdev_features_strings); if (ops && ops->get_sset_count && ops->get_strings) return ops->get_sset_count(dev, sset); else return -EOPNOTSUPP; } static void __ethtool_get_strings(struct net_device *dev, u32 stringset, u8 *data) { const struct ethtool_ops *ops = dev->ethtool_ops; if (stringset == ETH_SS_FEATURES) memcpy(data, netdev_features_strings, sizeof(netdev_features_strings)); else /* ops->get_strings is valid because checked earlier */ ops->get_strings(dev, stringset, data); } static u32 ethtool_get_feature_mask(u32 eth_cmd) { /* feature masks of legacy discrete ethtool ops */ switch (eth_cmd) { case ETHTOOL_GTXCSUM: case ETHTOOL_STXCSUM: return NETIF_F_ALL_CSUM | NETIF_F_SCTP_CSUM; case ETHTOOL_GRXCSUM: case ETHTOOL_SRXCSUM: return NETIF_F_RXCSUM; case ETHTOOL_GSG: case ETHTOOL_SSG: return NETIF_F_SG; case ETHTOOL_GTSO: case ETHTOOL_STSO: return NETIF_F_ALL_TSO; case ETHTOOL_GUFO: case ETHTOOL_SUFO: return NETIF_F_UFO; case ETHTOOL_GGSO: case ETHTOOL_SGSO: return NETIF_F_GSO; case ETHTOOL_GGRO: case ETHTOOL_SGRO: return NETIF_F_GRO; default: BUG(); } } static void *__ethtool_get_one_feature_actor(struct net_device *dev, u32 ethcmd) { const struct ethtool_ops *ops = dev->ethtool_ops; if (!ops) return NULL; switch (ethcmd) { case ETHTOOL_GTXCSUM: return ops->get_tx_csum; case ETHTOOL_GRXCSUM: return ops->get_rx_csum; case ETHTOOL_SSG: return ops->get_sg; case ETHTOOL_STSO: return ops->get_tso; case ETHTOOL_SUFO: return ops->get_ufo; default: return NULL; } } static u32 __ethtool_get_rx_csum_oldbug(struct net_device *dev) { return !!(dev->features & NETIF_F_ALL_CSUM); } static int ethtool_get_one_feature(struct net_device *dev, char __user *useraddr, u32 ethcmd) { u32 mask = ethtool_get_feature_mask(ethcmd); struct ethtool_value edata = { .cmd = ethcmd, .data = !!(dev->features & mask), }; /* compatibility with discrete get_ ops */ if (!(dev->hw_features & mask)) { u32 (*actor)(struct net_device *); actor = __ethtool_get_one_feature_actor(dev, ethcmd); /* bug compatibility with old get_rx_csum */ if (ethcmd == ETHTOOL_GRXCSUM && !actor) actor = __ethtool_get_rx_csum_oldbug; if (actor) edata.data = actor(dev); } if (copy_to_user(useraddr, &edata, sizeof(edata))) return -EFAULT; return 0; } static int __ethtool_set_tx_csum(struct net_device *dev, u32 data); static int __ethtool_set_rx_csum(struct net_device *dev, u32 data); static int __ethtool_set_sg(struct net_device *dev, u32 data); static int __ethtool_set_tso(struct net_device *dev, u32 data); static int __ethtool_set_ufo(struct net_device *dev, u32 data); static int ethtool_set_one_feature(struct net_device *dev, void __user *useraddr, u32 ethcmd) {