162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/core/ethtool.c - Ethtool ioctl handler 462306a36Sopenharmony_ci * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This file is where we call all the ethtool_ops commands to get 762306a36Sopenharmony_ci * the information ethtool needs. 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/compat.h> 1162306a36Sopenharmony_ci#include <linux/etherdevice.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/types.h> 1462306a36Sopenharmony_ci#include <linux/capability.h> 1562306a36Sopenharmony_ci#include <linux/errno.h> 1662306a36Sopenharmony_ci#include <linux/ethtool.h> 1762306a36Sopenharmony_ci#include <linux/netdevice.h> 1862306a36Sopenharmony_ci#include <linux/net_tstamp.h> 1962306a36Sopenharmony_ci#include <linux/phy.h> 2062306a36Sopenharmony_ci#include <linux/bitops.h> 2162306a36Sopenharmony_ci#include <linux/uaccess.h> 2262306a36Sopenharmony_ci#include <linux/vmalloc.h> 2362306a36Sopenharmony_ci#include <linux/sfp.h> 2462306a36Sopenharmony_ci#include <linux/slab.h> 2562306a36Sopenharmony_ci#include <linux/rtnetlink.h> 2662306a36Sopenharmony_ci#include <linux/sched/signal.h> 2762306a36Sopenharmony_ci#include <linux/net.h> 2862306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2962306a36Sopenharmony_ci#include <net/devlink.h> 3062306a36Sopenharmony_ci#include <net/ipv6.h> 3162306a36Sopenharmony_ci#include <net/xdp_sock_drv.h> 3262306a36Sopenharmony_ci#include <net/flow_offload.h> 3362306a36Sopenharmony_ci#include <linux/ethtool_netlink.h> 3462306a36Sopenharmony_ci#include <generated/utsrelease.h> 3562306a36Sopenharmony_ci#include "common.h" 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* State held across locks and calls for commands which have devlink fallback */ 3862306a36Sopenharmony_cistruct ethtool_devlink_compat { 3962306a36Sopenharmony_ci struct devlink *devlink; 4062306a36Sopenharmony_ci union { 4162306a36Sopenharmony_ci struct ethtool_flash efl; 4262306a36Sopenharmony_ci struct ethtool_drvinfo info; 4362306a36Sopenharmony_ci }; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic struct devlink *netdev_to_devlink_get(struct net_device *dev) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci if (!dev->devlink_port) 4962306a36Sopenharmony_ci return NULL; 5062306a36Sopenharmony_ci return devlink_try_get(dev->devlink_port->devlink); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci/* 5462306a36Sopenharmony_ci * Some useful ethtool_ops methods that're device independent. 5562306a36Sopenharmony_ci * If we find that all drivers want to do the same thing here, 5662306a36Sopenharmony_ci * we can turn these into dev_() function calls. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ciu32 ethtool_op_get_link(struct net_device *dev) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci return netif_carrier_ok(dev) ? 1 : 0; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_op_get_link); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ciint ethtool_op_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci info->so_timestamping = 6862306a36Sopenharmony_ci SOF_TIMESTAMPING_TX_SOFTWARE | 6962306a36Sopenharmony_ci SOF_TIMESTAMPING_RX_SOFTWARE | 7062306a36Sopenharmony_ci SOF_TIMESTAMPING_SOFTWARE; 7162306a36Sopenharmony_ci info->phc_index = -1; 7262306a36Sopenharmony_ci return 0; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_op_get_ts_info); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Handlers for each ethtool command */ 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic int ethtool_get_features(struct net_device *dev, void __user *useraddr) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct ethtool_gfeatures cmd = { 8162306a36Sopenharmony_ci .cmd = ETHTOOL_GFEATURES, 8262306a36Sopenharmony_ci .size = ETHTOOL_DEV_FEATURE_WORDS, 8362306a36Sopenharmony_ci }; 8462306a36Sopenharmony_ci struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; 8562306a36Sopenharmony_ci u32 __user *sizeaddr; 8662306a36Sopenharmony_ci u32 copy_size; 8762306a36Sopenharmony_ci int i; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* in case feature bits run out again */ 9062306a36Sopenharmony_ci BUILD_BUG_ON(ETHTOOL_DEV_FEATURE_WORDS * sizeof(u32) > sizeof(netdev_features_t)); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) { 9362306a36Sopenharmony_ci features[i].available = (u32)(dev->hw_features >> (32 * i)); 9462306a36Sopenharmony_ci features[i].requested = (u32)(dev->wanted_features >> (32 * i)); 9562306a36Sopenharmony_ci features[i].active = (u32)(dev->features >> (32 * i)); 9662306a36Sopenharmony_ci features[i].never_changed = 9762306a36Sopenharmony_ci (u32)(NETIF_F_NEVER_CHANGE >> (32 * i)); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size); 10162306a36Sopenharmony_ci if (get_user(copy_size, sizeaddr)) 10262306a36Sopenharmony_ci return -EFAULT; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (copy_size > ETHTOOL_DEV_FEATURE_WORDS) 10562306a36Sopenharmony_ci copy_size = ETHTOOL_DEV_FEATURE_WORDS; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci if (copy_to_user(useraddr, &cmd, sizeof(cmd))) 10862306a36Sopenharmony_ci return -EFAULT; 10962306a36Sopenharmony_ci useraddr += sizeof(cmd); 11062306a36Sopenharmony_ci if (copy_to_user(useraddr, features, 11162306a36Sopenharmony_ci array_size(copy_size, sizeof(*features)))) 11262306a36Sopenharmony_ci return -EFAULT; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci return 0; 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic int ethtool_set_features(struct net_device *dev, void __user *useraddr) 11862306a36Sopenharmony_ci{ 11962306a36Sopenharmony_ci struct ethtool_sfeatures cmd; 12062306a36Sopenharmony_ci struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS]; 12162306a36Sopenharmony_ci netdev_features_t wanted = 0, valid = 0; 12262306a36Sopenharmony_ci int i, ret = 0; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci if (copy_from_user(&cmd, useraddr, sizeof(cmd))) 12562306a36Sopenharmony_ci return -EFAULT; 12662306a36Sopenharmony_ci useraddr += sizeof(cmd); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS) 12962306a36Sopenharmony_ci return -EINVAL; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci if (copy_from_user(features, useraddr, sizeof(features))) 13262306a36Sopenharmony_ci return -EFAULT; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) { 13562306a36Sopenharmony_ci valid |= (netdev_features_t)features[i].valid << (32 * i); 13662306a36Sopenharmony_ci wanted |= (netdev_features_t)features[i].requested << (32 * i); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci if (valid & ~NETIF_F_ETHTOOL_BITS) 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (valid & ~dev->hw_features) { 14362306a36Sopenharmony_ci valid &= dev->hw_features; 14462306a36Sopenharmony_ci ret |= ETHTOOL_F_UNSUPPORTED; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci dev->wanted_features &= ~valid; 14862306a36Sopenharmony_ci dev->wanted_features |= wanted & valid; 14962306a36Sopenharmony_ci __netdev_update_features(dev); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if ((dev->wanted_features ^ dev->features) & valid) 15262306a36Sopenharmony_ci ret |= ETHTOOL_F_WISH; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci return ret; 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic int __ethtool_get_sset_count(struct net_device *dev, int sset) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops; 16062306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (sset == ETH_SS_FEATURES) 16362306a36Sopenharmony_ci return ARRAY_SIZE(netdev_features_strings); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (sset == ETH_SS_RSS_HASH_FUNCS) 16662306a36Sopenharmony_ci return ARRAY_SIZE(rss_hash_func_strings); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (sset == ETH_SS_TUNABLES) 16962306a36Sopenharmony_ci return ARRAY_SIZE(tunable_strings); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (sset == ETH_SS_PHY_TUNABLES) 17262306a36Sopenharmony_ci return ARRAY_SIZE(phy_tunable_strings); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci if (sset == ETH_SS_PHY_STATS && dev->phydev && 17562306a36Sopenharmony_ci !ops->get_ethtool_phy_stats && 17662306a36Sopenharmony_ci phy_ops && phy_ops->get_sset_count) 17762306a36Sopenharmony_ci return phy_ops->get_sset_count(dev->phydev); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (sset == ETH_SS_LINK_MODES) 18062306a36Sopenharmony_ci return __ETHTOOL_LINK_MODE_MASK_NBITS; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if (ops->get_sset_count && ops->get_strings) 18362306a36Sopenharmony_ci return ops->get_sset_count(dev, sset); 18462306a36Sopenharmony_ci else 18562306a36Sopenharmony_ci return -EOPNOTSUPP; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic void __ethtool_get_strings(struct net_device *dev, 18962306a36Sopenharmony_ci u32 stringset, u8 *data) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops; 19262306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (stringset == ETH_SS_FEATURES) 19562306a36Sopenharmony_ci memcpy(data, netdev_features_strings, 19662306a36Sopenharmony_ci sizeof(netdev_features_strings)); 19762306a36Sopenharmony_ci else if (stringset == ETH_SS_RSS_HASH_FUNCS) 19862306a36Sopenharmony_ci memcpy(data, rss_hash_func_strings, 19962306a36Sopenharmony_ci sizeof(rss_hash_func_strings)); 20062306a36Sopenharmony_ci else if (stringset == ETH_SS_TUNABLES) 20162306a36Sopenharmony_ci memcpy(data, tunable_strings, sizeof(tunable_strings)); 20262306a36Sopenharmony_ci else if (stringset == ETH_SS_PHY_TUNABLES) 20362306a36Sopenharmony_ci memcpy(data, phy_tunable_strings, sizeof(phy_tunable_strings)); 20462306a36Sopenharmony_ci else if (stringset == ETH_SS_PHY_STATS && dev->phydev && 20562306a36Sopenharmony_ci !ops->get_ethtool_phy_stats && phy_ops && 20662306a36Sopenharmony_ci phy_ops->get_strings) 20762306a36Sopenharmony_ci phy_ops->get_strings(dev->phydev, data); 20862306a36Sopenharmony_ci else if (stringset == ETH_SS_LINK_MODES) 20962306a36Sopenharmony_ci memcpy(data, link_mode_names, 21062306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS * ETH_GSTRING_LEN); 21162306a36Sopenharmony_ci else 21262306a36Sopenharmony_ci /* ops->get_strings is valid because checked earlier */ 21362306a36Sopenharmony_ci ops->get_strings(dev, stringset, data); 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic netdev_features_t ethtool_get_feature_mask(u32 eth_cmd) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci /* feature masks of legacy discrete ethtool ops */ 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci switch (eth_cmd) { 22162306a36Sopenharmony_ci case ETHTOOL_GTXCSUM: 22262306a36Sopenharmony_ci case ETHTOOL_STXCSUM: 22362306a36Sopenharmony_ci return NETIF_F_CSUM_MASK | NETIF_F_FCOE_CRC | 22462306a36Sopenharmony_ci NETIF_F_SCTP_CRC; 22562306a36Sopenharmony_ci case ETHTOOL_GRXCSUM: 22662306a36Sopenharmony_ci case ETHTOOL_SRXCSUM: 22762306a36Sopenharmony_ci return NETIF_F_RXCSUM; 22862306a36Sopenharmony_ci case ETHTOOL_GSG: 22962306a36Sopenharmony_ci case ETHTOOL_SSG: 23062306a36Sopenharmony_ci return NETIF_F_SG | NETIF_F_FRAGLIST; 23162306a36Sopenharmony_ci case ETHTOOL_GTSO: 23262306a36Sopenharmony_ci case ETHTOOL_STSO: 23362306a36Sopenharmony_ci return NETIF_F_ALL_TSO; 23462306a36Sopenharmony_ci case ETHTOOL_GGSO: 23562306a36Sopenharmony_ci case ETHTOOL_SGSO: 23662306a36Sopenharmony_ci return NETIF_F_GSO; 23762306a36Sopenharmony_ci case ETHTOOL_GGRO: 23862306a36Sopenharmony_ci case ETHTOOL_SGRO: 23962306a36Sopenharmony_ci return NETIF_F_GRO; 24062306a36Sopenharmony_ci default: 24162306a36Sopenharmony_ci BUG(); 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int ethtool_get_one_feature(struct net_device *dev, 24662306a36Sopenharmony_ci char __user *useraddr, u32 ethcmd) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci netdev_features_t mask = ethtool_get_feature_mask(ethcmd); 24962306a36Sopenharmony_ci struct ethtool_value edata = { 25062306a36Sopenharmony_ci .cmd = ethcmd, 25162306a36Sopenharmony_ci .data = !!(dev->features & mask), 25262306a36Sopenharmony_ci }; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (copy_to_user(useraddr, &edata, sizeof(edata))) 25562306a36Sopenharmony_ci return -EFAULT; 25662306a36Sopenharmony_ci return 0; 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic int ethtool_set_one_feature(struct net_device *dev, 26062306a36Sopenharmony_ci void __user *useraddr, u32 ethcmd) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci struct ethtool_value edata; 26362306a36Sopenharmony_ci netdev_features_t mask; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci if (copy_from_user(&edata, useraddr, sizeof(edata))) 26662306a36Sopenharmony_ci return -EFAULT; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci mask = ethtool_get_feature_mask(ethcmd); 26962306a36Sopenharmony_ci mask &= dev->hw_features; 27062306a36Sopenharmony_ci if (!mask) 27162306a36Sopenharmony_ci return -EOPNOTSUPP; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (edata.data) 27462306a36Sopenharmony_ci dev->wanted_features |= mask; 27562306a36Sopenharmony_ci else 27662306a36Sopenharmony_ci dev->wanted_features &= ~mask; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci __netdev_update_features(dev); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci return 0; 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci#define ETH_ALL_FLAGS (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | \ 28462306a36Sopenharmony_ci ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH) 28562306a36Sopenharmony_ci#define ETH_ALL_FEATURES (NETIF_F_LRO | NETIF_F_HW_VLAN_CTAG_RX | \ 28662306a36Sopenharmony_ci NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_NTUPLE | \ 28762306a36Sopenharmony_ci NETIF_F_RXHASH) 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic u32 __ethtool_get_flags(struct net_device *dev) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci u32 flags = 0; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (dev->features & NETIF_F_LRO) 29462306a36Sopenharmony_ci flags |= ETH_FLAG_LRO; 29562306a36Sopenharmony_ci if (dev->features & NETIF_F_HW_VLAN_CTAG_RX) 29662306a36Sopenharmony_ci flags |= ETH_FLAG_RXVLAN; 29762306a36Sopenharmony_ci if (dev->features & NETIF_F_HW_VLAN_CTAG_TX) 29862306a36Sopenharmony_ci flags |= ETH_FLAG_TXVLAN; 29962306a36Sopenharmony_ci if (dev->features & NETIF_F_NTUPLE) 30062306a36Sopenharmony_ci flags |= ETH_FLAG_NTUPLE; 30162306a36Sopenharmony_ci if (dev->features & NETIF_F_RXHASH) 30262306a36Sopenharmony_ci flags |= ETH_FLAG_RXHASH; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return flags; 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic int __ethtool_set_flags(struct net_device *dev, u32 data) 30862306a36Sopenharmony_ci{ 30962306a36Sopenharmony_ci netdev_features_t features = 0, changed; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci if (data & ~ETH_ALL_FLAGS) 31262306a36Sopenharmony_ci return -EINVAL; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (data & ETH_FLAG_LRO) 31562306a36Sopenharmony_ci features |= NETIF_F_LRO; 31662306a36Sopenharmony_ci if (data & ETH_FLAG_RXVLAN) 31762306a36Sopenharmony_ci features |= NETIF_F_HW_VLAN_CTAG_RX; 31862306a36Sopenharmony_ci if (data & ETH_FLAG_TXVLAN) 31962306a36Sopenharmony_ci features |= NETIF_F_HW_VLAN_CTAG_TX; 32062306a36Sopenharmony_ci if (data & ETH_FLAG_NTUPLE) 32162306a36Sopenharmony_ci features |= NETIF_F_NTUPLE; 32262306a36Sopenharmony_ci if (data & ETH_FLAG_RXHASH) 32362306a36Sopenharmony_ci features |= NETIF_F_RXHASH; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* allow changing only bits set in hw_features */ 32662306a36Sopenharmony_ci changed = (features ^ dev->features) & ETH_ALL_FEATURES; 32762306a36Sopenharmony_ci if (changed & ~dev->hw_features) 32862306a36Sopenharmony_ci return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci dev->wanted_features = 33162306a36Sopenharmony_ci (dev->wanted_features & ~changed) | (features & changed); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci __netdev_update_features(dev); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci return 0; 33662306a36Sopenharmony_ci} 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci/* Given two link masks, AND them together and save the result in dst. */ 33962306a36Sopenharmony_civoid ethtool_intersect_link_masks(struct ethtool_link_ksettings *dst, 34062306a36Sopenharmony_ci struct ethtool_link_ksettings *src) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci unsigned int size = BITS_TO_LONGS(__ETHTOOL_LINK_MODE_MASK_NBITS); 34362306a36Sopenharmony_ci unsigned int idx = 0; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci for (; idx < size; idx++) { 34662306a36Sopenharmony_ci dst->link_modes.supported[idx] &= 34762306a36Sopenharmony_ci src->link_modes.supported[idx]; 34862306a36Sopenharmony_ci dst->link_modes.advertising[idx] &= 34962306a36Sopenharmony_ci src->link_modes.advertising[idx]; 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_intersect_link_masks); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_civoid ethtool_convert_legacy_u32_to_link_mode(unsigned long *dst, 35562306a36Sopenharmony_ci u32 legacy_u32) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci linkmode_zero(dst); 35862306a36Sopenharmony_ci dst[0] = legacy_u32; 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_convert_legacy_u32_to_link_mode); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci/* return false if src had higher bits set. lower bits always updated. */ 36362306a36Sopenharmony_cibool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32, 36462306a36Sopenharmony_ci const unsigned long *src) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci *legacy_u32 = src[0]; 36762306a36Sopenharmony_ci return find_next_bit(src, __ETHTOOL_LINK_MODE_MASK_NBITS, 32) == 36862306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci/* return false if ksettings link modes had higher bits 37362306a36Sopenharmony_ci * set. legacy_settings always updated (best effort) 37462306a36Sopenharmony_ci */ 37562306a36Sopenharmony_cistatic bool 37662306a36Sopenharmony_ciconvert_link_ksettings_to_legacy_settings( 37762306a36Sopenharmony_ci struct ethtool_cmd *legacy_settings, 37862306a36Sopenharmony_ci const struct ethtool_link_ksettings *link_ksettings) 37962306a36Sopenharmony_ci{ 38062306a36Sopenharmony_ci bool retval = true; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci memset(legacy_settings, 0, sizeof(*legacy_settings)); 38362306a36Sopenharmony_ci /* this also clears the deprecated fields in legacy structure: 38462306a36Sopenharmony_ci * __u8 transceiver; 38562306a36Sopenharmony_ci * __u32 maxtxpkt; 38662306a36Sopenharmony_ci * __u32 maxrxpkt; 38762306a36Sopenharmony_ci */ 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci retval &= ethtool_convert_link_mode_to_legacy_u32( 39062306a36Sopenharmony_ci &legacy_settings->supported, 39162306a36Sopenharmony_ci link_ksettings->link_modes.supported); 39262306a36Sopenharmony_ci retval &= ethtool_convert_link_mode_to_legacy_u32( 39362306a36Sopenharmony_ci &legacy_settings->advertising, 39462306a36Sopenharmony_ci link_ksettings->link_modes.advertising); 39562306a36Sopenharmony_ci retval &= ethtool_convert_link_mode_to_legacy_u32( 39662306a36Sopenharmony_ci &legacy_settings->lp_advertising, 39762306a36Sopenharmony_ci link_ksettings->link_modes.lp_advertising); 39862306a36Sopenharmony_ci ethtool_cmd_speed_set(legacy_settings, link_ksettings->base.speed); 39962306a36Sopenharmony_ci legacy_settings->duplex 40062306a36Sopenharmony_ci = link_ksettings->base.duplex; 40162306a36Sopenharmony_ci legacy_settings->port 40262306a36Sopenharmony_ci = link_ksettings->base.port; 40362306a36Sopenharmony_ci legacy_settings->phy_address 40462306a36Sopenharmony_ci = link_ksettings->base.phy_address; 40562306a36Sopenharmony_ci legacy_settings->autoneg 40662306a36Sopenharmony_ci = link_ksettings->base.autoneg; 40762306a36Sopenharmony_ci legacy_settings->mdio_support 40862306a36Sopenharmony_ci = link_ksettings->base.mdio_support; 40962306a36Sopenharmony_ci legacy_settings->eth_tp_mdix 41062306a36Sopenharmony_ci = link_ksettings->base.eth_tp_mdix; 41162306a36Sopenharmony_ci legacy_settings->eth_tp_mdix_ctrl 41262306a36Sopenharmony_ci = link_ksettings->base.eth_tp_mdix_ctrl; 41362306a36Sopenharmony_ci legacy_settings->transceiver 41462306a36Sopenharmony_ci = link_ksettings->base.transceiver; 41562306a36Sopenharmony_ci return retval; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci/* number of 32-bit words to store the user's link mode bitmaps */ 41962306a36Sopenharmony_ci#define __ETHTOOL_LINK_MODE_MASK_NU32 \ 42062306a36Sopenharmony_ci DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32) 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci/* layout of the struct passed from/to userland */ 42362306a36Sopenharmony_cistruct ethtool_link_usettings { 42462306a36Sopenharmony_ci struct ethtool_link_settings base; 42562306a36Sopenharmony_ci struct { 42662306a36Sopenharmony_ci __u32 supported[__ETHTOOL_LINK_MODE_MASK_NU32]; 42762306a36Sopenharmony_ci __u32 advertising[__ETHTOOL_LINK_MODE_MASK_NU32]; 42862306a36Sopenharmony_ci __u32 lp_advertising[__ETHTOOL_LINK_MODE_MASK_NU32]; 42962306a36Sopenharmony_ci } link_modes; 43062306a36Sopenharmony_ci}; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci/* Internal kernel helper to query a device ethtool_link_settings. */ 43362306a36Sopenharmony_ciint __ethtool_get_link_ksettings(struct net_device *dev, 43462306a36Sopenharmony_ci struct ethtool_link_ksettings *link_ksettings) 43562306a36Sopenharmony_ci{ 43662306a36Sopenharmony_ci ASSERT_RTNL(); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (!dev->ethtool_ops->get_link_ksettings) 43962306a36Sopenharmony_ci return -EOPNOTSUPP; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci memset(link_ksettings, 0, sizeof(*link_ksettings)); 44262306a36Sopenharmony_ci return dev->ethtool_ops->get_link_ksettings(dev, link_ksettings); 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ciEXPORT_SYMBOL(__ethtool_get_link_ksettings); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci/* convert ethtool_link_usettings in user space to a kernel internal 44762306a36Sopenharmony_ci * ethtool_link_ksettings. return 0 on success, errno on error. 44862306a36Sopenharmony_ci */ 44962306a36Sopenharmony_cistatic int load_link_ksettings_from_user(struct ethtool_link_ksettings *to, 45062306a36Sopenharmony_ci const void __user *from) 45162306a36Sopenharmony_ci{ 45262306a36Sopenharmony_ci struct ethtool_link_usettings link_usettings; 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci if (copy_from_user(&link_usettings, from, sizeof(link_usettings))) 45562306a36Sopenharmony_ci return -EFAULT; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci memcpy(&to->base, &link_usettings.base, sizeof(to->base)); 45862306a36Sopenharmony_ci bitmap_from_arr32(to->link_modes.supported, 45962306a36Sopenharmony_ci link_usettings.link_modes.supported, 46062306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 46162306a36Sopenharmony_ci bitmap_from_arr32(to->link_modes.advertising, 46262306a36Sopenharmony_ci link_usettings.link_modes.advertising, 46362306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 46462306a36Sopenharmony_ci bitmap_from_arr32(to->link_modes.lp_advertising, 46562306a36Sopenharmony_ci link_usettings.link_modes.lp_advertising, 46662306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci return 0; 46962306a36Sopenharmony_ci} 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci/* Check if the user is trying to change anything besides speed/duplex */ 47262306a36Sopenharmony_cibool ethtool_virtdev_validate_cmd(const struct ethtool_link_ksettings *cmd) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct ethtool_link_settings base2 = {}; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci base2.speed = cmd->base.speed; 47762306a36Sopenharmony_ci base2.port = PORT_OTHER; 47862306a36Sopenharmony_ci base2.duplex = cmd->base.duplex; 47962306a36Sopenharmony_ci base2.cmd = cmd->base.cmd; 48062306a36Sopenharmony_ci base2.link_mode_masks_nwords = cmd->base.link_mode_masks_nwords; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return !memcmp(&base2, &cmd->base, sizeof(base2)) && 48362306a36Sopenharmony_ci bitmap_empty(cmd->link_modes.supported, 48462306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS) && 48562306a36Sopenharmony_ci bitmap_empty(cmd->link_modes.lp_advertising, 48662306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci/* convert a kernel internal ethtool_link_ksettings to 49062306a36Sopenharmony_ci * ethtool_link_usettings in user space. return 0 on success, errno on 49162306a36Sopenharmony_ci * error. 49262306a36Sopenharmony_ci */ 49362306a36Sopenharmony_cistatic int 49462306a36Sopenharmony_cistore_link_ksettings_for_user(void __user *to, 49562306a36Sopenharmony_ci const struct ethtool_link_ksettings *from) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci struct ethtool_link_usettings link_usettings; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci memcpy(&link_usettings, from, sizeof(link_usettings)); 50062306a36Sopenharmony_ci bitmap_to_arr32(link_usettings.link_modes.supported, 50162306a36Sopenharmony_ci from->link_modes.supported, 50262306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 50362306a36Sopenharmony_ci bitmap_to_arr32(link_usettings.link_modes.advertising, 50462306a36Sopenharmony_ci from->link_modes.advertising, 50562306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 50662306a36Sopenharmony_ci bitmap_to_arr32(link_usettings.link_modes.lp_advertising, 50762306a36Sopenharmony_ci from->link_modes.lp_advertising, 50862306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NBITS); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci if (copy_to_user(to, &link_usettings, sizeof(link_usettings))) 51162306a36Sopenharmony_ci return -EFAULT; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci return 0; 51462306a36Sopenharmony_ci} 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci/* Query device for its ethtool_link_settings. */ 51762306a36Sopenharmony_cistatic int ethtool_get_link_ksettings(struct net_device *dev, 51862306a36Sopenharmony_ci void __user *useraddr) 51962306a36Sopenharmony_ci{ 52062306a36Sopenharmony_ci int err = 0; 52162306a36Sopenharmony_ci struct ethtool_link_ksettings link_ksettings; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci ASSERT_RTNL(); 52462306a36Sopenharmony_ci if (!dev->ethtool_ops->get_link_ksettings) 52562306a36Sopenharmony_ci return -EOPNOTSUPP; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci /* handle bitmap nbits handshake */ 52862306a36Sopenharmony_ci if (copy_from_user(&link_ksettings.base, useraddr, 52962306a36Sopenharmony_ci sizeof(link_ksettings.base))) 53062306a36Sopenharmony_ci return -EFAULT; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci if (__ETHTOOL_LINK_MODE_MASK_NU32 53362306a36Sopenharmony_ci != link_ksettings.base.link_mode_masks_nwords) { 53462306a36Sopenharmony_ci /* wrong link mode nbits requested */ 53562306a36Sopenharmony_ci memset(&link_ksettings, 0, sizeof(link_ksettings)); 53662306a36Sopenharmony_ci link_ksettings.base.cmd = ETHTOOL_GLINKSETTINGS; 53762306a36Sopenharmony_ci /* send back number of words required as negative val */ 53862306a36Sopenharmony_ci compiletime_assert(__ETHTOOL_LINK_MODE_MASK_NU32 <= S8_MAX, 53962306a36Sopenharmony_ci "need too many bits for link modes!"); 54062306a36Sopenharmony_ci link_ksettings.base.link_mode_masks_nwords 54162306a36Sopenharmony_ci = -((s8)__ETHTOOL_LINK_MODE_MASK_NU32); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* copy the base fields back to user, not the link 54462306a36Sopenharmony_ci * mode bitmaps 54562306a36Sopenharmony_ci */ 54662306a36Sopenharmony_ci if (copy_to_user(useraddr, &link_ksettings.base, 54762306a36Sopenharmony_ci sizeof(link_ksettings.base))) 54862306a36Sopenharmony_ci return -EFAULT; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci return 0; 55162306a36Sopenharmony_ci } 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci /* handshake successful: user/kernel agree on 55462306a36Sopenharmony_ci * link_mode_masks_nwords 55562306a36Sopenharmony_ci */ 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci memset(&link_ksettings, 0, sizeof(link_ksettings)); 55862306a36Sopenharmony_ci err = dev->ethtool_ops->get_link_ksettings(dev, &link_ksettings); 55962306a36Sopenharmony_ci if (err < 0) 56062306a36Sopenharmony_ci return err; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci /* make sure we tell the right values to user */ 56362306a36Sopenharmony_ci link_ksettings.base.cmd = ETHTOOL_GLINKSETTINGS; 56462306a36Sopenharmony_ci link_ksettings.base.link_mode_masks_nwords 56562306a36Sopenharmony_ci = __ETHTOOL_LINK_MODE_MASK_NU32; 56662306a36Sopenharmony_ci link_ksettings.base.master_slave_cfg = MASTER_SLAVE_CFG_UNSUPPORTED; 56762306a36Sopenharmony_ci link_ksettings.base.master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED; 56862306a36Sopenharmony_ci link_ksettings.base.rate_matching = RATE_MATCH_NONE; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci return store_link_ksettings_for_user(useraddr, &link_ksettings); 57162306a36Sopenharmony_ci} 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci/* Update device ethtool_link_settings. */ 57462306a36Sopenharmony_cistatic int ethtool_set_link_ksettings(struct net_device *dev, 57562306a36Sopenharmony_ci void __user *useraddr) 57662306a36Sopenharmony_ci{ 57762306a36Sopenharmony_ci struct ethtool_link_ksettings link_ksettings = {}; 57862306a36Sopenharmony_ci int err; 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci ASSERT_RTNL(); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if (!dev->ethtool_ops->set_link_ksettings) 58362306a36Sopenharmony_ci return -EOPNOTSUPP; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci /* make sure nbits field has expected value */ 58662306a36Sopenharmony_ci if (copy_from_user(&link_ksettings.base, useraddr, 58762306a36Sopenharmony_ci sizeof(link_ksettings.base))) 58862306a36Sopenharmony_ci return -EFAULT; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci if (__ETHTOOL_LINK_MODE_MASK_NU32 59162306a36Sopenharmony_ci != link_ksettings.base.link_mode_masks_nwords) 59262306a36Sopenharmony_ci return -EINVAL; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci /* copy the whole structure, now that we know it has expected 59562306a36Sopenharmony_ci * format 59662306a36Sopenharmony_ci */ 59762306a36Sopenharmony_ci err = load_link_ksettings_from_user(&link_ksettings, useraddr); 59862306a36Sopenharmony_ci if (err) 59962306a36Sopenharmony_ci return err; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci /* re-check nwords field, just in case */ 60262306a36Sopenharmony_ci if (__ETHTOOL_LINK_MODE_MASK_NU32 60362306a36Sopenharmony_ci != link_ksettings.base.link_mode_masks_nwords) 60462306a36Sopenharmony_ci return -EINVAL; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci if (link_ksettings.base.master_slave_cfg || 60762306a36Sopenharmony_ci link_ksettings.base.master_slave_state) 60862306a36Sopenharmony_ci return -EINVAL; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci err = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings); 61162306a36Sopenharmony_ci if (err >= 0) { 61262306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL); 61362306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL); 61462306a36Sopenharmony_ci } 61562306a36Sopenharmony_ci return err; 61662306a36Sopenharmony_ci} 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ciint ethtool_virtdev_set_link_ksettings(struct net_device *dev, 61962306a36Sopenharmony_ci const struct ethtool_link_ksettings *cmd, 62062306a36Sopenharmony_ci u32 *dev_speed, u8 *dev_duplex) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci u32 speed; 62362306a36Sopenharmony_ci u8 duplex; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci speed = cmd->base.speed; 62662306a36Sopenharmony_ci duplex = cmd->base.duplex; 62762306a36Sopenharmony_ci /* don't allow custom speed and duplex */ 62862306a36Sopenharmony_ci if (!ethtool_validate_speed(speed) || 62962306a36Sopenharmony_ci !ethtool_validate_duplex(duplex) || 63062306a36Sopenharmony_ci !ethtool_virtdev_validate_cmd(cmd)) 63162306a36Sopenharmony_ci return -EINVAL; 63262306a36Sopenharmony_ci *dev_speed = speed; 63362306a36Sopenharmony_ci *dev_duplex = duplex; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci return 0; 63662306a36Sopenharmony_ci} 63762306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_virtdev_set_link_ksettings); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci/* Query device for its ethtool_cmd settings. 64062306a36Sopenharmony_ci * 64162306a36Sopenharmony_ci * Backward compatibility note: for compatibility with legacy ethtool, this is 64262306a36Sopenharmony_ci * now implemented via get_link_ksettings. When driver reports higher link mode 64362306a36Sopenharmony_ci * bits, a kernel warning is logged once (with name of 1st driver/device) to 64462306a36Sopenharmony_ci * recommend user to upgrade ethtool, but the command is successful (only the 64562306a36Sopenharmony_ci * lower link mode bits reported back to user). Deprecated fields from 64662306a36Sopenharmony_ci * ethtool_cmd (transceiver/maxrxpkt/maxtxpkt) are always set to zero. 64762306a36Sopenharmony_ci */ 64862306a36Sopenharmony_cistatic int ethtool_get_settings(struct net_device *dev, void __user *useraddr) 64962306a36Sopenharmony_ci{ 65062306a36Sopenharmony_ci struct ethtool_link_ksettings link_ksettings; 65162306a36Sopenharmony_ci struct ethtool_cmd cmd; 65262306a36Sopenharmony_ci int err; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci ASSERT_RTNL(); 65562306a36Sopenharmony_ci if (!dev->ethtool_ops->get_link_ksettings) 65662306a36Sopenharmony_ci return -EOPNOTSUPP; 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci memset(&link_ksettings, 0, sizeof(link_ksettings)); 65962306a36Sopenharmony_ci err = dev->ethtool_ops->get_link_ksettings(dev, &link_ksettings); 66062306a36Sopenharmony_ci if (err < 0) 66162306a36Sopenharmony_ci return err; 66262306a36Sopenharmony_ci convert_link_ksettings_to_legacy_settings(&cmd, &link_ksettings); 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci /* send a sensible cmd tag back to user */ 66562306a36Sopenharmony_ci cmd.cmd = ETHTOOL_GSET; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci if (copy_to_user(useraddr, &cmd, sizeof(cmd))) 66862306a36Sopenharmony_ci return -EFAULT; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci return 0; 67162306a36Sopenharmony_ci} 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci/* Update device link settings with given ethtool_cmd. 67462306a36Sopenharmony_ci * 67562306a36Sopenharmony_ci * Backward compatibility note: for compatibility with legacy ethtool, this is 67662306a36Sopenharmony_ci * now always implemented via set_link_settings. When user's request updates 67762306a36Sopenharmony_ci * deprecated ethtool_cmd fields (transceiver/maxrxpkt/maxtxpkt), a kernel 67862306a36Sopenharmony_ci * warning is logged once (with name of 1st driver/device) to recommend user to 67962306a36Sopenharmony_ci * upgrade ethtool, and the request is rejected. 68062306a36Sopenharmony_ci */ 68162306a36Sopenharmony_cistatic int ethtool_set_settings(struct net_device *dev, void __user *useraddr) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci struct ethtool_link_ksettings link_ksettings; 68462306a36Sopenharmony_ci struct ethtool_cmd cmd; 68562306a36Sopenharmony_ci int ret; 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci ASSERT_RTNL(); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci if (copy_from_user(&cmd, useraddr, sizeof(cmd))) 69062306a36Sopenharmony_ci return -EFAULT; 69162306a36Sopenharmony_ci if (!dev->ethtool_ops->set_link_ksettings) 69262306a36Sopenharmony_ci return -EOPNOTSUPP; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci if (!convert_legacy_settings_to_link_ksettings(&link_ksettings, &cmd)) 69562306a36Sopenharmony_ci return -EINVAL; 69662306a36Sopenharmony_ci link_ksettings.base.link_mode_masks_nwords = 69762306a36Sopenharmony_ci __ETHTOOL_LINK_MODE_MASK_NU32; 69862306a36Sopenharmony_ci ret = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings); 69962306a36Sopenharmony_ci if (ret >= 0) { 70062306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF, NULL); 70162306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF, NULL); 70262306a36Sopenharmony_ci } 70362306a36Sopenharmony_ci return ret; 70462306a36Sopenharmony_ci} 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_cistatic int 70762306a36Sopenharmony_ciethtool_get_drvinfo(struct net_device *dev, struct ethtool_devlink_compat *rsp) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 71062306a36Sopenharmony_ci struct device *parent = dev->dev.parent; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci rsp->info.cmd = ETHTOOL_GDRVINFO; 71362306a36Sopenharmony_ci strscpy(rsp->info.version, UTS_RELEASE, sizeof(rsp->info.version)); 71462306a36Sopenharmony_ci if (ops->get_drvinfo) { 71562306a36Sopenharmony_ci ops->get_drvinfo(dev, &rsp->info); 71662306a36Sopenharmony_ci if (!rsp->info.bus_info[0] && parent) 71762306a36Sopenharmony_ci strscpy(rsp->info.bus_info, dev_name(parent), 71862306a36Sopenharmony_ci sizeof(rsp->info.bus_info)); 71962306a36Sopenharmony_ci if (!rsp->info.driver[0] && parent && parent->driver) 72062306a36Sopenharmony_ci strscpy(rsp->info.driver, parent->driver->name, 72162306a36Sopenharmony_ci sizeof(rsp->info.driver)); 72262306a36Sopenharmony_ci } else if (parent && parent->driver) { 72362306a36Sopenharmony_ci strscpy(rsp->info.bus_info, dev_name(parent), 72462306a36Sopenharmony_ci sizeof(rsp->info.bus_info)); 72562306a36Sopenharmony_ci strscpy(rsp->info.driver, parent->driver->name, 72662306a36Sopenharmony_ci sizeof(rsp->info.driver)); 72762306a36Sopenharmony_ci } else if (dev->rtnl_link_ops) { 72862306a36Sopenharmony_ci strscpy(rsp->info.driver, dev->rtnl_link_ops->kind, 72962306a36Sopenharmony_ci sizeof(rsp->info.driver)); 73062306a36Sopenharmony_ci } else { 73162306a36Sopenharmony_ci return -EOPNOTSUPP; 73262306a36Sopenharmony_ci } 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_ci /* 73562306a36Sopenharmony_ci * this method of obtaining string set info is deprecated; 73662306a36Sopenharmony_ci * Use ETHTOOL_GSSET_INFO instead. 73762306a36Sopenharmony_ci */ 73862306a36Sopenharmony_ci if (ops->get_sset_count) { 73962306a36Sopenharmony_ci int rc; 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci rc = ops->get_sset_count(dev, ETH_SS_TEST); 74262306a36Sopenharmony_ci if (rc >= 0) 74362306a36Sopenharmony_ci rsp->info.testinfo_len = rc; 74462306a36Sopenharmony_ci rc = ops->get_sset_count(dev, ETH_SS_STATS); 74562306a36Sopenharmony_ci if (rc >= 0) 74662306a36Sopenharmony_ci rsp->info.n_stats = rc; 74762306a36Sopenharmony_ci rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS); 74862306a36Sopenharmony_ci if (rc >= 0) 74962306a36Sopenharmony_ci rsp->info.n_priv_flags = rc; 75062306a36Sopenharmony_ci } 75162306a36Sopenharmony_ci if (ops->get_regs_len) { 75262306a36Sopenharmony_ci int ret = ops->get_regs_len(dev); 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci if (ret > 0) 75562306a36Sopenharmony_ci rsp->info.regdump_len = ret; 75662306a36Sopenharmony_ci } 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci if (ops->get_eeprom_len) 75962306a36Sopenharmony_ci rsp->info.eedump_len = ops->get_eeprom_len(dev); 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci if (!rsp->info.fw_version[0]) 76262306a36Sopenharmony_ci rsp->devlink = netdev_to_devlink_get(dev); 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_ci return 0; 76562306a36Sopenharmony_ci} 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_sset_info(struct net_device *dev, 76862306a36Sopenharmony_ci void __user *useraddr) 76962306a36Sopenharmony_ci{ 77062306a36Sopenharmony_ci struct ethtool_sset_info info; 77162306a36Sopenharmony_ci u64 sset_mask; 77262306a36Sopenharmony_ci int i, idx = 0, n_bits = 0, ret, rc; 77362306a36Sopenharmony_ci u32 *info_buf = NULL; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci if (copy_from_user(&info, useraddr, sizeof(info))) 77662306a36Sopenharmony_ci return -EFAULT; 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci /* store copy of mask, because we zero struct later on */ 77962306a36Sopenharmony_ci sset_mask = info.sset_mask; 78062306a36Sopenharmony_ci if (!sset_mask) 78162306a36Sopenharmony_ci return 0; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci /* calculate size of return buffer */ 78462306a36Sopenharmony_ci n_bits = hweight64(sset_mask); 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 78762306a36Sopenharmony_ci info.cmd = ETHTOOL_GSSET_INFO; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci info_buf = kcalloc(n_bits, sizeof(u32), GFP_USER); 79062306a36Sopenharmony_ci if (!info_buf) 79162306a36Sopenharmony_ci return -ENOMEM; 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci /* 79462306a36Sopenharmony_ci * fill return buffer based on input bitmask and successful 79562306a36Sopenharmony_ci * get_sset_count return 79662306a36Sopenharmony_ci */ 79762306a36Sopenharmony_ci for (i = 0; i < 64; i++) { 79862306a36Sopenharmony_ci if (!(sset_mask & (1ULL << i))) 79962306a36Sopenharmony_ci continue; 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci rc = __ethtool_get_sset_count(dev, i); 80262306a36Sopenharmony_ci if (rc >= 0) { 80362306a36Sopenharmony_ci info.sset_mask |= (1ULL << i); 80462306a36Sopenharmony_ci info_buf[idx++] = rc; 80562306a36Sopenharmony_ci } 80662306a36Sopenharmony_ci } 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci ret = -EFAULT; 80962306a36Sopenharmony_ci if (copy_to_user(useraddr, &info, sizeof(info))) 81062306a36Sopenharmony_ci goto out; 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci useraddr += offsetof(struct ethtool_sset_info, data); 81362306a36Sopenharmony_ci if (copy_to_user(useraddr, info_buf, array_size(idx, sizeof(u32)))) 81462306a36Sopenharmony_ci goto out; 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci ret = 0; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ciout: 81962306a36Sopenharmony_ci kfree(info_buf); 82062306a36Sopenharmony_ci return ret; 82162306a36Sopenharmony_ci} 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_cistatic noinline_for_stack int 82462306a36Sopenharmony_ciethtool_rxnfc_copy_from_compat(struct ethtool_rxnfc *rxnfc, 82562306a36Sopenharmony_ci const struct compat_ethtool_rxnfc __user *useraddr, 82662306a36Sopenharmony_ci size_t size) 82762306a36Sopenharmony_ci{ 82862306a36Sopenharmony_ci struct compat_ethtool_rxnfc crxnfc = {}; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci /* We expect there to be holes between fs.m_ext and 83162306a36Sopenharmony_ci * fs.ring_cookie and at the end of fs, but nowhere else. 83262306a36Sopenharmony_ci * On non-x86, no conversion should be needed. 83362306a36Sopenharmony_ci */ 83462306a36Sopenharmony_ci BUILD_BUG_ON(!IS_ENABLED(CONFIG_X86_64) && 83562306a36Sopenharmony_ci sizeof(struct compat_ethtool_rxnfc) != 83662306a36Sopenharmony_ci sizeof(struct ethtool_rxnfc)); 83762306a36Sopenharmony_ci BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) + 83862306a36Sopenharmony_ci sizeof(useraddr->fs.m_ext) != 83962306a36Sopenharmony_ci offsetof(struct ethtool_rxnfc, fs.m_ext) + 84062306a36Sopenharmony_ci sizeof(rxnfc->fs.m_ext)); 84162306a36Sopenharmony_ci BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.location) - 84262306a36Sopenharmony_ci offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) != 84362306a36Sopenharmony_ci offsetof(struct ethtool_rxnfc, fs.location) - 84462306a36Sopenharmony_ci offsetof(struct ethtool_rxnfc, fs.ring_cookie)); 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc)))) 84762306a36Sopenharmony_ci return -EFAULT; 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci *rxnfc = (struct ethtool_rxnfc) { 85062306a36Sopenharmony_ci .cmd = crxnfc.cmd, 85162306a36Sopenharmony_ci .flow_type = crxnfc.flow_type, 85262306a36Sopenharmony_ci .data = crxnfc.data, 85362306a36Sopenharmony_ci .fs = { 85462306a36Sopenharmony_ci .flow_type = crxnfc.fs.flow_type, 85562306a36Sopenharmony_ci .h_u = crxnfc.fs.h_u, 85662306a36Sopenharmony_ci .h_ext = crxnfc.fs.h_ext, 85762306a36Sopenharmony_ci .m_u = crxnfc.fs.m_u, 85862306a36Sopenharmony_ci .m_ext = crxnfc.fs.m_ext, 85962306a36Sopenharmony_ci .ring_cookie = crxnfc.fs.ring_cookie, 86062306a36Sopenharmony_ci .location = crxnfc.fs.location, 86162306a36Sopenharmony_ci }, 86262306a36Sopenharmony_ci .rule_cnt = crxnfc.rule_cnt, 86362306a36Sopenharmony_ci }; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci return 0; 86662306a36Sopenharmony_ci} 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_cistatic int ethtool_rxnfc_copy_from_user(struct ethtool_rxnfc *rxnfc, 86962306a36Sopenharmony_ci const void __user *useraddr, 87062306a36Sopenharmony_ci size_t size) 87162306a36Sopenharmony_ci{ 87262306a36Sopenharmony_ci if (compat_need_64bit_alignment_fixup()) 87362306a36Sopenharmony_ci return ethtool_rxnfc_copy_from_compat(rxnfc, useraddr, size); 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci if (copy_from_user(rxnfc, useraddr, size)) 87662306a36Sopenharmony_ci return -EFAULT; 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci return 0; 87962306a36Sopenharmony_ci} 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_cistatic int ethtool_rxnfc_copy_to_compat(void __user *useraddr, 88262306a36Sopenharmony_ci const struct ethtool_rxnfc *rxnfc, 88362306a36Sopenharmony_ci size_t size, const u32 *rule_buf) 88462306a36Sopenharmony_ci{ 88562306a36Sopenharmony_ci struct compat_ethtool_rxnfc crxnfc; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci memset(&crxnfc, 0, sizeof(crxnfc)); 88862306a36Sopenharmony_ci crxnfc = (struct compat_ethtool_rxnfc) { 88962306a36Sopenharmony_ci .cmd = rxnfc->cmd, 89062306a36Sopenharmony_ci .flow_type = rxnfc->flow_type, 89162306a36Sopenharmony_ci .data = rxnfc->data, 89262306a36Sopenharmony_ci .fs = { 89362306a36Sopenharmony_ci .flow_type = rxnfc->fs.flow_type, 89462306a36Sopenharmony_ci .h_u = rxnfc->fs.h_u, 89562306a36Sopenharmony_ci .h_ext = rxnfc->fs.h_ext, 89662306a36Sopenharmony_ci .m_u = rxnfc->fs.m_u, 89762306a36Sopenharmony_ci .m_ext = rxnfc->fs.m_ext, 89862306a36Sopenharmony_ci .ring_cookie = rxnfc->fs.ring_cookie, 89962306a36Sopenharmony_ci .location = rxnfc->fs.location, 90062306a36Sopenharmony_ci }, 90162306a36Sopenharmony_ci .rule_cnt = rxnfc->rule_cnt, 90262306a36Sopenharmony_ci }; 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc)))) 90562306a36Sopenharmony_ci return -EFAULT; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci return 0; 90862306a36Sopenharmony_ci} 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_cistatic int ethtool_rxnfc_copy_struct(u32 cmd, struct ethtool_rxnfc *info, 91162306a36Sopenharmony_ci size_t *info_size, void __user *useraddr) 91262306a36Sopenharmony_ci{ 91362306a36Sopenharmony_ci /* struct ethtool_rxnfc was originally defined for 91462306a36Sopenharmony_ci * ETHTOOL_{G,S}RXFH with only the cmd, flow_type and data 91562306a36Sopenharmony_ci * members. User-space might still be using that 91662306a36Sopenharmony_ci * definition. 91762306a36Sopenharmony_ci */ 91862306a36Sopenharmony_ci if (cmd == ETHTOOL_GRXFH || cmd == ETHTOOL_SRXFH) 91962306a36Sopenharmony_ci *info_size = (offsetof(struct ethtool_rxnfc, data) + 92062306a36Sopenharmony_ci sizeof(info->data)); 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci if (ethtool_rxnfc_copy_from_user(info, useraddr, *info_size)) 92362306a36Sopenharmony_ci return -EFAULT; 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci if ((cmd == ETHTOOL_GRXFH || cmd == ETHTOOL_SRXFH) && info->flow_type & FLOW_RSS) { 92662306a36Sopenharmony_ci *info_size = sizeof(*info); 92762306a36Sopenharmony_ci if (ethtool_rxnfc_copy_from_user(info, useraddr, *info_size)) 92862306a36Sopenharmony_ci return -EFAULT; 92962306a36Sopenharmony_ci /* Since malicious users may modify the original data, 93062306a36Sopenharmony_ci * we need to check whether FLOW_RSS is still requested. 93162306a36Sopenharmony_ci */ 93262306a36Sopenharmony_ci if (!(info->flow_type & FLOW_RSS)) 93362306a36Sopenharmony_ci return -EINVAL; 93462306a36Sopenharmony_ci } 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_ci if (info->cmd != cmd) 93762306a36Sopenharmony_ci return -EINVAL; 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci return 0; 94062306a36Sopenharmony_ci} 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_cistatic int ethtool_rxnfc_copy_to_user(void __user *useraddr, 94362306a36Sopenharmony_ci const struct ethtool_rxnfc *rxnfc, 94462306a36Sopenharmony_ci size_t size, const u32 *rule_buf) 94562306a36Sopenharmony_ci{ 94662306a36Sopenharmony_ci int ret; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci if (compat_need_64bit_alignment_fixup()) { 94962306a36Sopenharmony_ci ret = ethtool_rxnfc_copy_to_compat(useraddr, rxnfc, size, 95062306a36Sopenharmony_ci rule_buf); 95162306a36Sopenharmony_ci useraddr += offsetof(struct compat_ethtool_rxnfc, rule_locs); 95262306a36Sopenharmony_ci } else { 95362306a36Sopenharmony_ci ret = copy_to_user(useraddr, rxnfc, size); 95462306a36Sopenharmony_ci useraddr += offsetof(struct ethtool_rxnfc, rule_locs); 95562306a36Sopenharmony_ci } 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci if (ret) 95862306a36Sopenharmony_ci return -EFAULT; 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci if (rule_buf) { 96162306a36Sopenharmony_ci if (copy_to_user(useraddr, rule_buf, 96262306a36Sopenharmony_ci rxnfc->rule_cnt * sizeof(u32))) 96362306a36Sopenharmony_ci return -EFAULT; 96462306a36Sopenharmony_ci } 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci return 0; 96762306a36Sopenharmony_ci} 96862306a36Sopenharmony_ci 96962306a36Sopenharmony_cistatic noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev, 97062306a36Sopenharmony_ci u32 cmd, void __user *useraddr) 97162306a36Sopenharmony_ci{ 97262306a36Sopenharmony_ci struct ethtool_rxnfc info; 97362306a36Sopenharmony_ci size_t info_size = sizeof(info); 97462306a36Sopenharmony_ci int rc; 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci if (!dev->ethtool_ops->set_rxnfc) 97762306a36Sopenharmony_ci return -EOPNOTSUPP; 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci rc = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr); 98062306a36Sopenharmony_ci if (rc) 98162306a36Sopenharmony_ci return rc; 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci rc = dev->ethtool_ops->set_rxnfc(dev, &info); 98462306a36Sopenharmony_ci if (rc) 98562306a36Sopenharmony_ci return rc; 98662306a36Sopenharmony_ci 98762306a36Sopenharmony_ci if (cmd == ETHTOOL_SRXCLSRLINS && 98862306a36Sopenharmony_ci ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL)) 98962306a36Sopenharmony_ci return -EFAULT; 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci return 0; 99262306a36Sopenharmony_ci} 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev, 99562306a36Sopenharmony_ci u32 cmd, void __user *useraddr) 99662306a36Sopenharmony_ci{ 99762306a36Sopenharmony_ci struct ethtool_rxnfc info; 99862306a36Sopenharmony_ci size_t info_size = sizeof(info); 99962306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 100062306a36Sopenharmony_ci int ret; 100162306a36Sopenharmony_ci void *rule_buf = NULL; 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ci if (!ops->get_rxnfc) 100462306a36Sopenharmony_ci return -EOPNOTSUPP; 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci ret = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr); 100762306a36Sopenharmony_ci if (ret) 100862306a36Sopenharmony_ci return ret; 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci if (info.cmd == ETHTOOL_GRXCLSRLALL) { 101162306a36Sopenharmony_ci if (info.rule_cnt > 0) { 101262306a36Sopenharmony_ci if (info.rule_cnt <= KMALLOC_MAX_SIZE / sizeof(u32)) 101362306a36Sopenharmony_ci rule_buf = kcalloc(info.rule_cnt, sizeof(u32), 101462306a36Sopenharmony_ci GFP_USER); 101562306a36Sopenharmony_ci if (!rule_buf) 101662306a36Sopenharmony_ci return -ENOMEM; 101762306a36Sopenharmony_ci } 101862306a36Sopenharmony_ci } 101962306a36Sopenharmony_ci 102062306a36Sopenharmony_ci ret = ops->get_rxnfc(dev, &info, rule_buf); 102162306a36Sopenharmony_ci if (ret < 0) 102262306a36Sopenharmony_ci goto err_out; 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_ci ret = ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, rule_buf); 102562306a36Sopenharmony_cierr_out: 102662306a36Sopenharmony_ci kfree(rule_buf); 102762306a36Sopenharmony_ci 102862306a36Sopenharmony_ci return ret; 102962306a36Sopenharmony_ci} 103062306a36Sopenharmony_ci 103162306a36Sopenharmony_cistatic int ethtool_copy_validate_indir(u32 *indir, void __user *useraddr, 103262306a36Sopenharmony_ci struct ethtool_rxnfc *rx_rings, 103362306a36Sopenharmony_ci u32 size) 103462306a36Sopenharmony_ci{ 103562306a36Sopenharmony_ci int i; 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci if (copy_from_user(indir, useraddr, array_size(size, sizeof(indir[0])))) 103862306a36Sopenharmony_ci return -EFAULT; 103962306a36Sopenharmony_ci 104062306a36Sopenharmony_ci /* Validate ring indices */ 104162306a36Sopenharmony_ci for (i = 0; i < size; i++) 104262306a36Sopenharmony_ci if (indir[i] >= rx_rings->data) 104362306a36Sopenharmony_ci return -EINVAL; 104462306a36Sopenharmony_ci 104562306a36Sopenharmony_ci return 0; 104662306a36Sopenharmony_ci} 104762306a36Sopenharmony_ci 104862306a36Sopenharmony_ciu8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly; 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_civoid netdev_rss_key_fill(void *buffer, size_t len) 105162306a36Sopenharmony_ci{ 105262306a36Sopenharmony_ci BUG_ON(len > sizeof(netdev_rss_key)); 105362306a36Sopenharmony_ci net_get_random_once(netdev_rss_key, sizeof(netdev_rss_key)); 105462306a36Sopenharmony_ci memcpy(buffer, netdev_rss_key, len); 105562306a36Sopenharmony_ci} 105662306a36Sopenharmony_ciEXPORT_SYMBOL(netdev_rss_key_fill); 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev, 105962306a36Sopenharmony_ci void __user *useraddr) 106062306a36Sopenharmony_ci{ 106162306a36Sopenharmony_ci u32 user_size, dev_size; 106262306a36Sopenharmony_ci u32 *indir; 106362306a36Sopenharmony_ci int ret; 106462306a36Sopenharmony_ci 106562306a36Sopenharmony_ci if (!dev->ethtool_ops->get_rxfh_indir_size || 106662306a36Sopenharmony_ci !dev->ethtool_ops->get_rxfh) 106762306a36Sopenharmony_ci return -EOPNOTSUPP; 106862306a36Sopenharmony_ci dev_size = dev->ethtool_ops->get_rxfh_indir_size(dev); 106962306a36Sopenharmony_ci if (dev_size == 0) 107062306a36Sopenharmony_ci return -EOPNOTSUPP; 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_ci if (copy_from_user(&user_size, 107362306a36Sopenharmony_ci useraddr + offsetof(struct ethtool_rxfh_indir, size), 107462306a36Sopenharmony_ci sizeof(user_size))) 107562306a36Sopenharmony_ci return -EFAULT; 107662306a36Sopenharmony_ci 107762306a36Sopenharmony_ci if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh_indir, size), 107862306a36Sopenharmony_ci &dev_size, sizeof(dev_size))) 107962306a36Sopenharmony_ci return -EFAULT; 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci /* If the user buffer size is 0, this is just a query for the 108262306a36Sopenharmony_ci * device table size. Otherwise, if it's smaller than the 108362306a36Sopenharmony_ci * device table size it's an error. 108462306a36Sopenharmony_ci */ 108562306a36Sopenharmony_ci if (user_size < dev_size) 108662306a36Sopenharmony_ci return user_size == 0 ? 0 : -EINVAL; 108762306a36Sopenharmony_ci 108862306a36Sopenharmony_ci indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER); 108962306a36Sopenharmony_ci if (!indir) 109062306a36Sopenharmony_ci return -ENOMEM; 109162306a36Sopenharmony_ci 109262306a36Sopenharmony_ci ret = dev->ethtool_ops->get_rxfh(dev, indir, NULL, NULL); 109362306a36Sopenharmony_ci if (ret) 109462306a36Sopenharmony_ci goto out; 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_ci if (copy_to_user(useraddr + 109762306a36Sopenharmony_ci offsetof(struct ethtool_rxfh_indir, ring_index[0]), 109862306a36Sopenharmony_ci indir, dev_size * sizeof(indir[0]))) 109962306a36Sopenharmony_ci ret = -EFAULT; 110062306a36Sopenharmony_ci 110162306a36Sopenharmony_ciout: 110262306a36Sopenharmony_ci kfree(indir); 110362306a36Sopenharmony_ci return ret; 110462306a36Sopenharmony_ci} 110562306a36Sopenharmony_ci 110662306a36Sopenharmony_cistatic noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev, 110762306a36Sopenharmony_ci void __user *useraddr) 110862306a36Sopenharmony_ci{ 110962306a36Sopenharmony_ci struct ethtool_rxnfc rx_rings; 111062306a36Sopenharmony_ci u32 user_size, dev_size, i; 111162306a36Sopenharmony_ci u32 *indir; 111262306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 111362306a36Sopenharmony_ci int ret; 111462306a36Sopenharmony_ci u32 ringidx_offset = offsetof(struct ethtool_rxfh_indir, ring_index[0]); 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci if (!ops->get_rxfh_indir_size || !ops->set_rxfh || 111762306a36Sopenharmony_ci !ops->get_rxnfc) 111862306a36Sopenharmony_ci return -EOPNOTSUPP; 111962306a36Sopenharmony_ci 112062306a36Sopenharmony_ci dev_size = ops->get_rxfh_indir_size(dev); 112162306a36Sopenharmony_ci if (dev_size == 0) 112262306a36Sopenharmony_ci return -EOPNOTSUPP; 112362306a36Sopenharmony_ci 112462306a36Sopenharmony_ci if (copy_from_user(&user_size, 112562306a36Sopenharmony_ci useraddr + offsetof(struct ethtool_rxfh_indir, size), 112662306a36Sopenharmony_ci sizeof(user_size))) 112762306a36Sopenharmony_ci return -EFAULT; 112862306a36Sopenharmony_ci 112962306a36Sopenharmony_ci if (user_size != 0 && user_size != dev_size) 113062306a36Sopenharmony_ci return -EINVAL; 113162306a36Sopenharmony_ci 113262306a36Sopenharmony_ci indir = kcalloc(dev_size, sizeof(indir[0]), GFP_USER); 113362306a36Sopenharmony_ci if (!indir) 113462306a36Sopenharmony_ci return -ENOMEM; 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci rx_rings.cmd = ETHTOOL_GRXRINGS; 113762306a36Sopenharmony_ci ret = ops->get_rxnfc(dev, &rx_rings, NULL); 113862306a36Sopenharmony_ci if (ret) 113962306a36Sopenharmony_ci goto out; 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci if (user_size == 0) { 114262306a36Sopenharmony_ci for (i = 0; i < dev_size; i++) 114362306a36Sopenharmony_ci indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); 114462306a36Sopenharmony_ci } else { 114562306a36Sopenharmony_ci ret = ethtool_copy_validate_indir(indir, 114662306a36Sopenharmony_ci useraddr + ringidx_offset, 114762306a36Sopenharmony_ci &rx_rings, 114862306a36Sopenharmony_ci dev_size); 114962306a36Sopenharmony_ci if (ret) 115062306a36Sopenharmony_ci goto out; 115162306a36Sopenharmony_ci } 115262306a36Sopenharmony_ci 115362306a36Sopenharmony_ci ret = ops->set_rxfh(dev, indir, NULL, ETH_RSS_HASH_NO_CHANGE); 115462306a36Sopenharmony_ci if (ret) 115562306a36Sopenharmony_ci goto out; 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci /* indicate whether rxfh was set to default */ 115862306a36Sopenharmony_ci if (user_size == 0) 115962306a36Sopenharmony_ci dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 116062306a36Sopenharmony_ci else 116162306a36Sopenharmony_ci dev->priv_flags |= IFF_RXFH_CONFIGURED; 116262306a36Sopenharmony_ci 116362306a36Sopenharmony_ciout: 116462306a36Sopenharmony_ci kfree(indir); 116562306a36Sopenharmony_ci return ret; 116662306a36Sopenharmony_ci} 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_rxfh(struct net_device *dev, 116962306a36Sopenharmony_ci void __user *useraddr) 117062306a36Sopenharmony_ci{ 117162306a36Sopenharmony_ci int ret; 117262306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 117362306a36Sopenharmony_ci u32 user_indir_size, user_key_size; 117462306a36Sopenharmony_ci u32 dev_indir_size = 0, dev_key_size = 0; 117562306a36Sopenharmony_ci struct ethtool_rxfh rxfh; 117662306a36Sopenharmony_ci u32 total_size; 117762306a36Sopenharmony_ci u32 indir_bytes; 117862306a36Sopenharmony_ci u32 *indir = NULL; 117962306a36Sopenharmony_ci u8 dev_hfunc = 0; 118062306a36Sopenharmony_ci u8 *hkey = NULL; 118162306a36Sopenharmony_ci u8 *rss_config; 118262306a36Sopenharmony_ci 118362306a36Sopenharmony_ci if (!ops->get_rxfh) 118462306a36Sopenharmony_ci return -EOPNOTSUPP; 118562306a36Sopenharmony_ci 118662306a36Sopenharmony_ci if (ops->get_rxfh_indir_size) 118762306a36Sopenharmony_ci dev_indir_size = ops->get_rxfh_indir_size(dev); 118862306a36Sopenharmony_ci if (ops->get_rxfh_key_size) 118962306a36Sopenharmony_ci dev_key_size = ops->get_rxfh_key_size(dev); 119062306a36Sopenharmony_ci 119162306a36Sopenharmony_ci if (copy_from_user(&rxfh, useraddr, sizeof(rxfh))) 119262306a36Sopenharmony_ci return -EFAULT; 119362306a36Sopenharmony_ci user_indir_size = rxfh.indir_size; 119462306a36Sopenharmony_ci user_key_size = rxfh.key_size; 119562306a36Sopenharmony_ci 119662306a36Sopenharmony_ci /* Check that reserved fields are 0 for now */ 119762306a36Sopenharmony_ci if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd8[2] || rxfh.rsvd32) 119862306a36Sopenharmony_ci return -EINVAL; 119962306a36Sopenharmony_ci /* Most drivers don't handle rss_context, check it's 0 as well */ 120062306a36Sopenharmony_ci if (rxfh.rss_context && !ops->get_rxfh_context) 120162306a36Sopenharmony_ci return -EOPNOTSUPP; 120262306a36Sopenharmony_ci 120362306a36Sopenharmony_ci rxfh.indir_size = dev_indir_size; 120462306a36Sopenharmony_ci rxfh.key_size = dev_key_size; 120562306a36Sopenharmony_ci if (copy_to_user(useraddr, &rxfh, sizeof(rxfh))) 120662306a36Sopenharmony_ci return -EFAULT; 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_ci if ((user_indir_size && (user_indir_size != dev_indir_size)) || 120962306a36Sopenharmony_ci (user_key_size && (user_key_size != dev_key_size))) 121062306a36Sopenharmony_ci return -EINVAL; 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_ci indir_bytes = user_indir_size * sizeof(indir[0]); 121362306a36Sopenharmony_ci total_size = indir_bytes + user_key_size; 121462306a36Sopenharmony_ci rss_config = kzalloc(total_size, GFP_USER); 121562306a36Sopenharmony_ci if (!rss_config) 121662306a36Sopenharmony_ci return -ENOMEM; 121762306a36Sopenharmony_ci 121862306a36Sopenharmony_ci if (user_indir_size) 121962306a36Sopenharmony_ci indir = (u32 *)rss_config; 122062306a36Sopenharmony_ci 122162306a36Sopenharmony_ci if (user_key_size) 122262306a36Sopenharmony_ci hkey = rss_config + indir_bytes; 122362306a36Sopenharmony_ci 122462306a36Sopenharmony_ci if (rxfh.rss_context) 122562306a36Sopenharmony_ci ret = dev->ethtool_ops->get_rxfh_context(dev, indir, hkey, 122662306a36Sopenharmony_ci &dev_hfunc, 122762306a36Sopenharmony_ci rxfh.rss_context); 122862306a36Sopenharmony_ci else 122962306a36Sopenharmony_ci ret = dev->ethtool_ops->get_rxfh(dev, indir, hkey, &dev_hfunc); 123062306a36Sopenharmony_ci if (ret) 123162306a36Sopenharmony_ci goto out; 123262306a36Sopenharmony_ci 123362306a36Sopenharmony_ci if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, hfunc), 123462306a36Sopenharmony_ci &dev_hfunc, sizeof(rxfh.hfunc))) { 123562306a36Sopenharmony_ci ret = -EFAULT; 123662306a36Sopenharmony_ci } else if (copy_to_user(useraddr + 123762306a36Sopenharmony_ci offsetof(struct ethtool_rxfh, rss_config[0]), 123862306a36Sopenharmony_ci rss_config, total_size)) { 123962306a36Sopenharmony_ci ret = -EFAULT; 124062306a36Sopenharmony_ci } 124162306a36Sopenharmony_ciout: 124262306a36Sopenharmony_ci kfree(rss_config); 124362306a36Sopenharmony_ci 124462306a36Sopenharmony_ci return ret; 124562306a36Sopenharmony_ci} 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_cistatic noinline_for_stack int ethtool_set_rxfh(struct net_device *dev, 124862306a36Sopenharmony_ci void __user *useraddr) 124962306a36Sopenharmony_ci{ 125062306a36Sopenharmony_ci int ret; 125162306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 125262306a36Sopenharmony_ci struct ethtool_rxnfc rx_rings; 125362306a36Sopenharmony_ci struct ethtool_rxfh rxfh; 125462306a36Sopenharmony_ci u32 dev_indir_size = 0, dev_key_size = 0, i; 125562306a36Sopenharmony_ci u32 *indir = NULL, indir_bytes = 0; 125662306a36Sopenharmony_ci u8 *hkey = NULL; 125762306a36Sopenharmony_ci u8 *rss_config; 125862306a36Sopenharmony_ci u32 rss_cfg_offset = offsetof(struct ethtool_rxfh, rss_config[0]); 125962306a36Sopenharmony_ci bool delete = false; 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_ci if (!ops->get_rxnfc || !ops->set_rxfh) 126262306a36Sopenharmony_ci return -EOPNOTSUPP; 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci if (ops->get_rxfh_indir_size) 126562306a36Sopenharmony_ci dev_indir_size = ops->get_rxfh_indir_size(dev); 126662306a36Sopenharmony_ci if (ops->get_rxfh_key_size) 126762306a36Sopenharmony_ci dev_key_size = ops->get_rxfh_key_size(dev); 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_ci if (copy_from_user(&rxfh, useraddr, sizeof(rxfh))) 127062306a36Sopenharmony_ci return -EFAULT; 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_ci /* Check that reserved fields are 0 for now */ 127362306a36Sopenharmony_ci if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd8[2] || rxfh.rsvd32) 127462306a36Sopenharmony_ci return -EINVAL; 127562306a36Sopenharmony_ci /* Most drivers don't handle rss_context, check it's 0 as well */ 127662306a36Sopenharmony_ci if (rxfh.rss_context && !ops->set_rxfh_context) 127762306a36Sopenharmony_ci return -EOPNOTSUPP; 127862306a36Sopenharmony_ci 127962306a36Sopenharmony_ci /* If either indir, hash key or function is valid, proceed further. 128062306a36Sopenharmony_ci * Must request at least one change: indir size, hash key or function. 128162306a36Sopenharmony_ci */ 128262306a36Sopenharmony_ci if ((rxfh.indir_size && 128362306a36Sopenharmony_ci rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE && 128462306a36Sopenharmony_ci rxfh.indir_size != dev_indir_size) || 128562306a36Sopenharmony_ci (rxfh.key_size && (rxfh.key_size != dev_key_size)) || 128662306a36Sopenharmony_ci (rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE && 128762306a36Sopenharmony_ci rxfh.key_size == 0 && rxfh.hfunc == ETH_RSS_HASH_NO_CHANGE)) 128862306a36Sopenharmony_ci return -EINVAL; 128962306a36Sopenharmony_ci 129062306a36Sopenharmony_ci if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) 129162306a36Sopenharmony_ci indir_bytes = dev_indir_size * sizeof(indir[0]); 129262306a36Sopenharmony_ci 129362306a36Sopenharmony_ci rss_config = kzalloc(indir_bytes + rxfh.key_size, GFP_USER); 129462306a36Sopenharmony_ci if (!rss_config) 129562306a36Sopenharmony_ci return -ENOMEM; 129662306a36Sopenharmony_ci 129762306a36Sopenharmony_ci rx_rings.cmd = ETHTOOL_GRXRINGS; 129862306a36Sopenharmony_ci ret = ops->get_rxnfc(dev, &rx_rings, NULL); 129962306a36Sopenharmony_ci if (ret) 130062306a36Sopenharmony_ci goto out; 130162306a36Sopenharmony_ci 130262306a36Sopenharmony_ci /* rxfh.indir_size == 0 means reset the indir table to default (master 130362306a36Sopenharmony_ci * context) or delete the context (other RSS contexts). 130462306a36Sopenharmony_ci * rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE means leave it unchanged. 130562306a36Sopenharmony_ci */ 130662306a36Sopenharmony_ci if (rxfh.indir_size && 130762306a36Sopenharmony_ci rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) { 130862306a36Sopenharmony_ci indir = (u32 *)rss_config; 130962306a36Sopenharmony_ci ret = ethtool_copy_validate_indir(indir, 131062306a36Sopenharmony_ci useraddr + rss_cfg_offset, 131162306a36Sopenharmony_ci &rx_rings, 131262306a36Sopenharmony_ci rxfh.indir_size); 131362306a36Sopenharmony_ci if (ret) 131462306a36Sopenharmony_ci goto out; 131562306a36Sopenharmony_ci } else if (rxfh.indir_size == 0) { 131662306a36Sopenharmony_ci if (rxfh.rss_context == 0) { 131762306a36Sopenharmony_ci indir = (u32 *)rss_config; 131862306a36Sopenharmony_ci for (i = 0; i < dev_indir_size; i++) 131962306a36Sopenharmony_ci indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data); 132062306a36Sopenharmony_ci } else { 132162306a36Sopenharmony_ci delete = true; 132262306a36Sopenharmony_ci } 132362306a36Sopenharmony_ci } 132462306a36Sopenharmony_ci 132562306a36Sopenharmony_ci if (rxfh.key_size) { 132662306a36Sopenharmony_ci hkey = rss_config + indir_bytes; 132762306a36Sopenharmony_ci if (copy_from_user(hkey, 132862306a36Sopenharmony_ci useraddr + rss_cfg_offset + indir_bytes, 132962306a36Sopenharmony_ci rxfh.key_size)) { 133062306a36Sopenharmony_ci ret = -EFAULT; 133162306a36Sopenharmony_ci goto out; 133262306a36Sopenharmony_ci } 133362306a36Sopenharmony_ci } 133462306a36Sopenharmony_ci 133562306a36Sopenharmony_ci if (rxfh.rss_context) 133662306a36Sopenharmony_ci ret = ops->set_rxfh_context(dev, indir, hkey, rxfh.hfunc, 133762306a36Sopenharmony_ci &rxfh.rss_context, delete); 133862306a36Sopenharmony_ci else 133962306a36Sopenharmony_ci ret = ops->set_rxfh(dev, indir, hkey, rxfh.hfunc); 134062306a36Sopenharmony_ci if (ret) 134162306a36Sopenharmony_ci goto out; 134262306a36Sopenharmony_ci 134362306a36Sopenharmony_ci if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, rss_context), 134462306a36Sopenharmony_ci &rxfh.rss_context, sizeof(rxfh.rss_context))) 134562306a36Sopenharmony_ci ret = -EFAULT; 134662306a36Sopenharmony_ci 134762306a36Sopenharmony_ci if (!rxfh.rss_context) { 134862306a36Sopenharmony_ci /* indicate whether rxfh was set to default */ 134962306a36Sopenharmony_ci if (rxfh.indir_size == 0) 135062306a36Sopenharmony_ci dev->priv_flags &= ~IFF_RXFH_CONFIGURED; 135162306a36Sopenharmony_ci else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) 135262306a36Sopenharmony_ci dev->priv_flags |= IFF_RXFH_CONFIGURED; 135362306a36Sopenharmony_ci } 135462306a36Sopenharmony_ci 135562306a36Sopenharmony_ciout: 135662306a36Sopenharmony_ci kfree(rss_config); 135762306a36Sopenharmony_ci return ret; 135862306a36Sopenharmony_ci} 135962306a36Sopenharmony_ci 136062306a36Sopenharmony_cistatic int ethtool_get_regs(struct net_device *dev, char __user *useraddr) 136162306a36Sopenharmony_ci{ 136262306a36Sopenharmony_ci struct ethtool_regs regs; 136362306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 136462306a36Sopenharmony_ci void *regbuf; 136562306a36Sopenharmony_ci int reglen, ret; 136662306a36Sopenharmony_ci 136762306a36Sopenharmony_ci if (!ops->get_regs || !ops->get_regs_len) 136862306a36Sopenharmony_ci return -EOPNOTSUPP; 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_ci if (copy_from_user(®s, useraddr, sizeof(regs))) 137162306a36Sopenharmony_ci return -EFAULT; 137262306a36Sopenharmony_ci 137362306a36Sopenharmony_ci reglen = ops->get_regs_len(dev); 137462306a36Sopenharmony_ci if (reglen <= 0) 137562306a36Sopenharmony_ci return reglen; 137662306a36Sopenharmony_ci 137762306a36Sopenharmony_ci if (regs.len > reglen) 137862306a36Sopenharmony_ci regs.len = reglen; 137962306a36Sopenharmony_ci 138062306a36Sopenharmony_ci regbuf = vzalloc(reglen); 138162306a36Sopenharmony_ci if (!regbuf) 138262306a36Sopenharmony_ci return -ENOMEM; 138362306a36Sopenharmony_ci 138462306a36Sopenharmony_ci if (regs.len < reglen) 138562306a36Sopenharmony_ci reglen = regs.len; 138662306a36Sopenharmony_ci 138762306a36Sopenharmony_ci ops->get_regs(dev, ®s, regbuf); 138862306a36Sopenharmony_ci 138962306a36Sopenharmony_ci ret = -EFAULT; 139062306a36Sopenharmony_ci if (copy_to_user(useraddr, ®s, sizeof(regs))) 139162306a36Sopenharmony_ci goto out; 139262306a36Sopenharmony_ci useraddr += offsetof(struct ethtool_regs, data); 139362306a36Sopenharmony_ci if (copy_to_user(useraddr, regbuf, reglen)) 139462306a36Sopenharmony_ci goto out; 139562306a36Sopenharmony_ci ret = 0; 139662306a36Sopenharmony_ci 139762306a36Sopenharmony_ci out: 139862306a36Sopenharmony_ci vfree(regbuf); 139962306a36Sopenharmony_ci return ret; 140062306a36Sopenharmony_ci} 140162306a36Sopenharmony_ci 140262306a36Sopenharmony_cistatic int ethtool_reset(struct net_device *dev, char __user *useraddr) 140362306a36Sopenharmony_ci{ 140462306a36Sopenharmony_ci struct ethtool_value reset; 140562306a36Sopenharmony_ci int ret; 140662306a36Sopenharmony_ci 140762306a36Sopenharmony_ci if (!dev->ethtool_ops->reset) 140862306a36Sopenharmony_ci return -EOPNOTSUPP; 140962306a36Sopenharmony_ci 141062306a36Sopenharmony_ci if (copy_from_user(&reset, useraddr, sizeof(reset))) 141162306a36Sopenharmony_ci return -EFAULT; 141262306a36Sopenharmony_ci 141362306a36Sopenharmony_ci ret = dev->ethtool_ops->reset(dev, &reset.data); 141462306a36Sopenharmony_ci if (ret) 141562306a36Sopenharmony_ci return ret; 141662306a36Sopenharmony_ci 141762306a36Sopenharmony_ci if (copy_to_user(useraddr, &reset, sizeof(reset))) 141862306a36Sopenharmony_ci return -EFAULT; 141962306a36Sopenharmony_ci return 0; 142062306a36Sopenharmony_ci} 142162306a36Sopenharmony_ci 142262306a36Sopenharmony_cistatic int ethtool_get_wol(struct net_device *dev, char __user *useraddr) 142362306a36Sopenharmony_ci{ 142462306a36Sopenharmony_ci struct ethtool_wolinfo wol; 142562306a36Sopenharmony_ci 142662306a36Sopenharmony_ci if (!dev->ethtool_ops->get_wol) 142762306a36Sopenharmony_ci return -EOPNOTSUPP; 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_ci memset(&wol, 0, sizeof(struct ethtool_wolinfo)); 143062306a36Sopenharmony_ci wol.cmd = ETHTOOL_GWOL; 143162306a36Sopenharmony_ci dev->ethtool_ops->get_wol(dev, &wol); 143262306a36Sopenharmony_ci 143362306a36Sopenharmony_ci if (copy_to_user(useraddr, &wol, sizeof(wol))) 143462306a36Sopenharmony_ci return -EFAULT; 143562306a36Sopenharmony_ci return 0; 143662306a36Sopenharmony_ci} 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_cistatic int ethtool_set_wol(struct net_device *dev, char __user *useraddr) 143962306a36Sopenharmony_ci{ 144062306a36Sopenharmony_ci struct ethtool_wolinfo wol, cur_wol; 144162306a36Sopenharmony_ci int ret; 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci if (!dev->ethtool_ops->get_wol || !dev->ethtool_ops->set_wol) 144462306a36Sopenharmony_ci return -EOPNOTSUPP; 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci memset(&cur_wol, 0, sizeof(struct ethtool_wolinfo)); 144762306a36Sopenharmony_ci cur_wol.cmd = ETHTOOL_GWOL; 144862306a36Sopenharmony_ci dev->ethtool_ops->get_wol(dev, &cur_wol); 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci if (copy_from_user(&wol, useraddr, sizeof(wol))) 145162306a36Sopenharmony_ci return -EFAULT; 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_ci if (wol.wolopts & ~cur_wol.supported) 145462306a36Sopenharmony_ci return -EINVAL; 145562306a36Sopenharmony_ci 145662306a36Sopenharmony_ci if (wol.wolopts == cur_wol.wolopts && 145762306a36Sopenharmony_ci !memcmp(wol.sopass, cur_wol.sopass, sizeof(wol.sopass))) 145862306a36Sopenharmony_ci return 0; 145962306a36Sopenharmony_ci 146062306a36Sopenharmony_ci ret = dev->ethtool_ops->set_wol(dev, &wol); 146162306a36Sopenharmony_ci if (ret) 146262306a36Sopenharmony_ci return ret; 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci dev->wol_enabled = !!wol.wolopts; 146562306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_WOL_NTF, NULL); 146662306a36Sopenharmony_ci 146762306a36Sopenharmony_ci return 0; 146862306a36Sopenharmony_ci} 146962306a36Sopenharmony_ci 147062306a36Sopenharmony_cistatic int ethtool_get_eee(struct net_device *dev, char __user *useraddr) 147162306a36Sopenharmony_ci{ 147262306a36Sopenharmony_ci struct ethtool_eee edata; 147362306a36Sopenharmony_ci int rc; 147462306a36Sopenharmony_ci 147562306a36Sopenharmony_ci if (!dev->ethtool_ops->get_eee) 147662306a36Sopenharmony_ci return -EOPNOTSUPP; 147762306a36Sopenharmony_ci 147862306a36Sopenharmony_ci memset(&edata, 0, sizeof(struct ethtool_eee)); 147962306a36Sopenharmony_ci edata.cmd = ETHTOOL_GEEE; 148062306a36Sopenharmony_ci rc = dev->ethtool_ops->get_eee(dev, &edata); 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_ci if (rc) 148362306a36Sopenharmony_ci return rc; 148462306a36Sopenharmony_ci 148562306a36Sopenharmony_ci if (copy_to_user(useraddr, &edata, sizeof(edata))) 148662306a36Sopenharmony_ci return -EFAULT; 148762306a36Sopenharmony_ci 148862306a36Sopenharmony_ci return 0; 148962306a36Sopenharmony_ci} 149062306a36Sopenharmony_ci 149162306a36Sopenharmony_cistatic int ethtool_set_eee(struct net_device *dev, char __user *useraddr) 149262306a36Sopenharmony_ci{ 149362306a36Sopenharmony_ci struct ethtool_eee edata; 149462306a36Sopenharmony_ci int ret; 149562306a36Sopenharmony_ci 149662306a36Sopenharmony_ci if (!dev->ethtool_ops->set_eee) 149762306a36Sopenharmony_ci return -EOPNOTSUPP; 149862306a36Sopenharmony_ci 149962306a36Sopenharmony_ci if (copy_from_user(&edata, useraddr, sizeof(edata))) 150062306a36Sopenharmony_ci return -EFAULT; 150162306a36Sopenharmony_ci 150262306a36Sopenharmony_ci ret = dev->ethtool_ops->set_eee(dev, &edata); 150362306a36Sopenharmony_ci if (!ret) 150462306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_EEE_NTF, NULL); 150562306a36Sopenharmony_ci return ret; 150662306a36Sopenharmony_ci} 150762306a36Sopenharmony_ci 150862306a36Sopenharmony_cistatic int ethtool_nway_reset(struct net_device *dev) 150962306a36Sopenharmony_ci{ 151062306a36Sopenharmony_ci if (!dev->ethtool_ops->nway_reset) 151162306a36Sopenharmony_ci return -EOPNOTSUPP; 151262306a36Sopenharmony_ci 151362306a36Sopenharmony_ci return dev->ethtool_ops->nway_reset(dev); 151462306a36Sopenharmony_ci} 151562306a36Sopenharmony_ci 151662306a36Sopenharmony_cistatic int ethtool_get_link(struct net_device *dev, char __user *useraddr) 151762306a36Sopenharmony_ci{ 151862306a36Sopenharmony_ci struct ethtool_value edata = { .cmd = ETHTOOL_GLINK }; 151962306a36Sopenharmony_ci int link = __ethtool_get_link(dev); 152062306a36Sopenharmony_ci 152162306a36Sopenharmony_ci if (link < 0) 152262306a36Sopenharmony_ci return link; 152362306a36Sopenharmony_ci 152462306a36Sopenharmony_ci edata.data = link; 152562306a36Sopenharmony_ci if (copy_to_user(useraddr, &edata, sizeof(edata))) 152662306a36Sopenharmony_ci return -EFAULT; 152762306a36Sopenharmony_ci return 0; 152862306a36Sopenharmony_ci} 152962306a36Sopenharmony_ci 153062306a36Sopenharmony_cistatic int ethtool_get_any_eeprom(struct net_device *dev, void __user *useraddr, 153162306a36Sopenharmony_ci int (*getter)(struct net_device *, 153262306a36Sopenharmony_ci struct ethtool_eeprom *, u8 *), 153362306a36Sopenharmony_ci u32 total_len) 153462306a36Sopenharmony_ci{ 153562306a36Sopenharmony_ci struct ethtool_eeprom eeprom; 153662306a36Sopenharmony_ci void __user *userbuf = useraddr + sizeof(eeprom); 153762306a36Sopenharmony_ci u32 bytes_remaining; 153862306a36Sopenharmony_ci u8 *data; 153962306a36Sopenharmony_ci int ret = 0; 154062306a36Sopenharmony_ci 154162306a36Sopenharmony_ci if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) 154262306a36Sopenharmony_ci return -EFAULT; 154362306a36Sopenharmony_ci 154462306a36Sopenharmony_ci /* Check for wrap and zero */ 154562306a36Sopenharmony_ci if (eeprom.offset + eeprom.len <= eeprom.offset) 154662306a36Sopenharmony_ci return -EINVAL; 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci /* Check for exceeding total eeprom len */ 154962306a36Sopenharmony_ci if (eeprom.offset + eeprom.len > total_len) 155062306a36Sopenharmony_ci return -EINVAL; 155162306a36Sopenharmony_ci 155262306a36Sopenharmony_ci data = kzalloc(PAGE_SIZE, GFP_USER); 155362306a36Sopenharmony_ci if (!data) 155462306a36Sopenharmony_ci return -ENOMEM; 155562306a36Sopenharmony_ci 155662306a36Sopenharmony_ci bytes_remaining = eeprom.len; 155762306a36Sopenharmony_ci while (bytes_remaining > 0) { 155862306a36Sopenharmony_ci eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE); 155962306a36Sopenharmony_ci 156062306a36Sopenharmony_ci ret = getter(dev, &eeprom, data); 156162306a36Sopenharmony_ci if (ret) 156262306a36Sopenharmony_ci break; 156362306a36Sopenharmony_ci if (!eeprom.len) { 156462306a36Sopenharmony_ci ret = -EIO; 156562306a36Sopenharmony_ci break; 156662306a36Sopenharmony_ci } 156762306a36Sopenharmony_ci if (copy_to_user(userbuf, data, eeprom.len)) { 156862306a36Sopenharmony_ci ret = -EFAULT; 156962306a36Sopenharmony_ci break; 157062306a36Sopenharmony_ci } 157162306a36Sopenharmony_ci userbuf += eeprom.len; 157262306a36Sopenharmony_ci eeprom.offset += eeprom.len; 157362306a36Sopenharmony_ci bytes_remaining -= eeprom.len; 157462306a36Sopenharmony_ci } 157562306a36Sopenharmony_ci 157662306a36Sopenharmony_ci eeprom.len = userbuf - (useraddr + sizeof(eeprom)); 157762306a36Sopenharmony_ci eeprom.offset -= eeprom.len; 157862306a36Sopenharmony_ci if (copy_to_user(useraddr, &eeprom, sizeof(eeprom))) 157962306a36Sopenharmony_ci ret = -EFAULT; 158062306a36Sopenharmony_ci 158162306a36Sopenharmony_ci kfree(data); 158262306a36Sopenharmony_ci return ret; 158362306a36Sopenharmony_ci} 158462306a36Sopenharmony_ci 158562306a36Sopenharmony_cistatic int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr) 158662306a36Sopenharmony_ci{ 158762306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 158862306a36Sopenharmony_ci 158962306a36Sopenharmony_ci if (!ops->get_eeprom || !ops->get_eeprom_len || 159062306a36Sopenharmony_ci !ops->get_eeprom_len(dev)) 159162306a36Sopenharmony_ci return -EOPNOTSUPP; 159262306a36Sopenharmony_ci 159362306a36Sopenharmony_ci return ethtool_get_any_eeprom(dev, useraddr, ops->get_eeprom, 159462306a36Sopenharmony_ci ops->get_eeprom_len(dev)); 159562306a36Sopenharmony_ci} 159662306a36Sopenharmony_ci 159762306a36Sopenharmony_cistatic int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr) 159862306a36Sopenharmony_ci{ 159962306a36Sopenharmony_ci struct ethtool_eeprom eeprom; 160062306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 160162306a36Sopenharmony_ci void __user *userbuf = useraddr + sizeof(eeprom); 160262306a36Sopenharmony_ci u32 bytes_remaining; 160362306a36Sopenharmony_ci u8 *data; 160462306a36Sopenharmony_ci int ret = 0; 160562306a36Sopenharmony_ci 160662306a36Sopenharmony_ci if (!ops->set_eeprom || !ops->get_eeprom_len || 160762306a36Sopenharmony_ci !ops->get_eeprom_len(dev)) 160862306a36Sopenharmony_ci return -EOPNOTSUPP; 160962306a36Sopenharmony_ci 161062306a36Sopenharmony_ci if (copy_from_user(&eeprom, useraddr, sizeof(eeprom))) 161162306a36Sopenharmony_ci return -EFAULT; 161262306a36Sopenharmony_ci 161362306a36Sopenharmony_ci /* Check for wrap and zero */ 161462306a36Sopenharmony_ci if (eeprom.offset + eeprom.len <= eeprom.offset) 161562306a36Sopenharmony_ci return -EINVAL; 161662306a36Sopenharmony_ci 161762306a36Sopenharmony_ci /* Check for exceeding total eeprom len */ 161862306a36Sopenharmony_ci if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev)) 161962306a36Sopenharmony_ci return -EINVAL; 162062306a36Sopenharmony_ci 162162306a36Sopenharmony_ci data = kzalloc(PAGE_SIZE, GFP_USER); 162262306a36Sopenharmony_ci if (!data) 162362306a36Sopenharmony_ci return -ENOMEM; 162462306a36Sopenharmony_ci 162562306a36Sopenharmony_ci bytes_remaining = eeprom.len; 162662306a36Sopenharmony_ci while (bytes_remaining > 0) { 162762306a36Sopenharmony_ci eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE); 162862306a36Sopenharmony_ci 162962306a36Sopenharmony_ci if (copy_from_user(data, userbuf, eeprom.len)) { 163062306a36Sopenharmony_ci ret = -EFAULT; 163162306a36Sopenharmony_ci break; 163262306a36Sopenharmony_ci } 163362306a36Sopenharmony_ci ret = ops->set_eeprom(dev, &eeprom, data); 163462306a36Sopenharmony_ci if (ret) 163562306a36Sopenharmony_ci break; 163662306a36Sopenharmony_ci userbuf += eeprom.len; 163762306a36Sopenharmony_ci eeprom.offset += eeprom.len; 163862306a36Sopenharmony_ci bytes_remaining -= eeprom.len; 163962306a36Sopenharmony_ci } 164062306a36Sopenharmony_ci 164162306a36Sopenharmony_ci kfree(data); 164262306a36Sopenharmony_ci return ret; 164362306a36Sopenharmony_ci} 164462306a36Sopenharmony_ci 164562306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_coalesce(struct net_device *dev, 164662306a36Sopenharmony_ci void __user *useraddr) 164762306a36Sopenharmony_ci{ 164862306a36Sopenharmony_ci struct ethtool_coalesce coalesce = { .cmd = ETHTOOL_GCOALESCE }; 164962306a36Sopenharmony_ci struct kernel_ethtool_coalesce kernel_coalesce = {}; 165062306a36Sopenharmony_ci int ret; 165162306a36Sopenharmony_ci 165262306a36Sopenharmony_ci if (!dev->ethtool_ops->get_coalesce) 165362306a36Sopenharmony_ci return -EOPNOTSUPP; 165462306a36Sopenharmony_ci 165562306a36Sopenharmony_ci ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce, 165662306a36Sopenharmony_ci NULL); 165762306a36Sopenharmony_ci if (ret) 165862306a36Sopenharmony_ci return ret; 165962306a36Sopenharmony_ci 166062306a36Sopenharmony_ci if (copy_to_user(useraddr, &coalesce, sizeof(coalesce))) 166162306a36Sopenharmony_ci return -EFAULT; 166262306a36Sopenharmony_ci return 0; 166362306a36Sopenharmony_ci} 166462306a36Sopenharmony_ci 166562306a36Sopenharmony_cistatic bool 166662306a36Sopenharmony_ciethtool_set_coalesce_supported(struct net_device *dev, 166762306a36Sopenharmony_ci struct ethtool_coalesce *coalesce) 166862306a36Sopenharmony_ci{ 166962306a36Sopenharmony_ci u32 supported_params = dev->ethtool_ops->supported_coalesce_params; 167062306a36Sopenharmony_ci u32 nonzero_params = 0; 167162306a36Sopenharmony_ci 167262306a36Sopenharmony_ci if (coalesce->rx_coalesce_usecs) 167362306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_USECS; 167462306a36Sopenharmony_ci if (coalesce->rx_max_coalesced_frames) 167562306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES; 167662306a36Sopenharmony_ci if (coalesce->rx_coalesce_usecs_irq) 167762306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_USECS_IRQ; 167862306a36Sopenharmony_ci if (coalesce->rx_max_coalesced_frames_irq) 167962306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ; 168062306a36Sopenharmony_ci if (coalesce->tx_coalesce_usecs) 168162306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_USECS; 168262306a36Sopenharmony_ci if (coalesce->tx_max_coalesced_frames) 168362306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES; 168462306a36Sopenharmony_ci if (coalesce->tx_coalesce_usecs_irq) 168562306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_USECS_IRQ; 168662306a36Sopenharmony_ci if (coalesce->tx_max_coalesced_frames_irq) 168762306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ; 168862306a36Sopenharmony_ci if (coalesce->stats_block_coalesce_usecs) 168962306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_STATS_BLOCK_USECS; 169062306a36Sopenharmony_ci if (coalesce->use_adaptive_rx_coalesce) 169162306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_USE_ADAPTIVE_RX; 169262306a36Sopenharmony_ci if (coalesce->use_adaptive_tx_coalesce) 169362306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_USE_ADAPTIVE_TX; 169462306a36Sopenharmony_ci if (coalesce->pkt_rate_low) 169562306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_PKT_RATE_LOW; 169662306a36Sopenharmony_ci if (coalesce->rx_coalesce_usecs_low) 169762306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_USECS_LOW; 169862306a36Sopenharmony_ci if (coalesce->rx_max_coalesced_frames_low) 169962306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW; 170062306a36Sopenharmony_ci if (coalesce->tx_coalesce_usecs_low) 170162306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_USECS_LOW; 170262306a36Sopenharmony_ci if (coalesce->tx_max_coalesced_frames_low) 170362306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_LOW; 170462306a36Sopenharmony_ci if (coalesce->pkt_rate_high) 170562306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_PKT_RATE_HIGH; 170662306a36Sopenharmony_ci if (coalesce->rx_coalesce_usecs_high) 170762306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_USECS_HIGH; 170862306a36Sopenharmony_ci if (coalesce->rx_max_coalesced_frames_high) 170962306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH; 171062306a36Sopenharmony_ci if (coalesce->tx_coalesce_usecs_high) 171162306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_USECS_HIGH; 171262306a36Sopenharmony_ci if (coalesce->tx_max_coalesced_frames_high) 171362306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_HIGH; 171462306a36Sopenharmony_ci if (coalesce->rate_sample_interval) 171562306a36Sopenharmony_ci nonzero_params |= ETHTOOL_COALESCE_RATE_SAMPLE_INTERVAL; 171662306a36Sopenharmony_ci 171762306a36Sopenharmony_ci return (supported_params & nonzero_params) == nonzero_params; 171862306a36Sopenharmony_ci} 171962306a36Sopenharmony_ci 172062306a36Sopenharmony_cistatic noinline_for_stack int ethtool_set_coalesce(struct net_device *dev, 172162306a36Sopenharmony_ci void __user *useraddr) 172262306a36Sopenharmony_ci{ 172362306a36Sopenharmony_ci struct kernel_ethtool_coalesce kernel_coalesce = {}; 172462306a36Sopenharmony_ci struct ethtool_coalesce coalesce; 172562306a36Sopenharmony_ci int ret; 172662306a36Sopenharmony_ci 172762306a36Sopenharmony_ci if (!dev->ethtool_ops->set_coalesce || !dev->ethtool_ops->get_coalesce) 172862306a36Sopenharmony_ci return -EOPNOTSUPP; 172962306a36Sopenharmony_ci 173062306a36Sopenharmony_ci ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce, 173162306a36Sopenharmony_ci NULL); 173262306a36Sopenharmony_ci if (ret) 173362306a36Sopenharmony_ci return ret; 173462306a36Sopenharmony_ci 173562306a36Sopenharmony_ci if (copy_from_user(&coalesce, useraddr, sizeof(coalesce))) 173662306a36Sopenharmony_ci return -EFAULT; 173762306a36Sopenharmony_ci 173862306a36Sopenharmony_ci if (!ethtool_set_coalesce_supported(dev, &coalesce)) 173962306a36Sopenharmony_ci return -EOPNOTSUPP; 174062306a36Sopenharmony_ci 174162306a36Sopenharmony_ci ret = dev->ethtool_ops->set_coalesce(dev, &coalesce, &kernel_coalesce, 174262306a36Sopenharmony_ci NULL); 174362306a36Sopenharmony_ci if (!ret) 174462306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_COALESCE_NTF, NULL); 174562306a36Sopenharmony_ci return ret; 174662306a36Sopenharmony_ci} 174762306a36Sopenharmony_ci 174862306a36Sopenharmony_cistatic int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr) 174962306a36Sopenharmony_ci{ 175062306a36Sopenharmony_ci struct ethtool_ringparam ringparam = { .cmd = ETHTOOL_GRINGPARAM }; 175162306a36Sopenharmony_ci struct kernel_ethtool_ringparam kernel_ringparam = {}; 175262306a36Sopenharmony_ci 175362306a36Sopenharmony_ci if (!dev->ethtool_ops->get_ringparam) 175462306a36Sopenharmony_ci return -EOPNOTSUPP; 175562306a36Sopenharmony_ci 175662306a36Sopenharmony_ci dev->ethtool_ops->get_ringparam(dev, &ringparam, 175762306a36Sopenharmony_ci &kernel_ringparam, NULL); 175862306a36Sopenharmony_ci 175962306a36Sopenharmony_ci if (copy_to_user(useraddr, &ringparam, sizeof(ringparam))) 176062306a36Sopenharmony_ci return -EFAULT; 176162306a36Sopenharmony_ci return 0; 176262306a36Sopenharmony_ci} 176362306a36Sopenharmony_ci 176462306a36Sopenharmony_cistatic int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr) 176562306a36Sopenharmony_ci{ 176662306a36Sopenharmony_ci struct ethtool_ringparam ringparam, max = { .cmd = ETHTOOL_GRINGPARAM }; 176762306a36Sopenharmony_ci struct kernel_ethtool_ringparam kernel_ringparam; 176862306a36Sopenharmony_ci int ret; 176962306a36Sopenharmony_ci 177062306a36Sopenharmony_ci if (!dev->ethtool_ops->set_ringparam || !dev->ethtool_ops->get_ringparam) 177162306a36Sopenharmony_ci return -EOPNOTSUPP; 177262306a36Sopenharmony_ci 177362306a36Sopenharmony_ci if (copy_from_user(&ringparam, useraddr, sizeof(ringparam))) 177462306a36Sopenharmony_ci return -EFAULT; 177562306a36Sopenharmony_ci 177662306a36Sopenharmony_ci dev->ethtool_ops->get_ringparam(dev, &max, &kernel_ringparam, NULL); 177762306a36Sopenharmony_ci 177862306a36Sopenharmony_ci /* ensure new ring parameters are within the maximums */ 177962306a36Sopenharmony_ci if (ringparam.rx_pending > max.rx_max_pending || 178062306a36Sopenharmony_ci ringparam.rx_mini_pending > max.rx_mini_max_pending || 178162306a36Sopenharmony_ci ringparam.rx_jumbo_pending > max.rx_jumbo_max_pending || 178262306a36Sopenharmony_ci ringparam.tx_pending > max.tx_max_pending) 178362306a36Sopenharmony_ci return -EINVAL; 178462306a36Sopenharmony_ci 178562306a36Sopenharmony_ci ret = dev->ethtool_ops->set_ringparam(dev, &ringparam, 178662306a36Sopenharmony_ci &kernel_ringparam, NULL); 178762306a36Sopenharmony_ci if (!ret) 178862306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF, NULL); 178962306a36Sopenharmony_ci return ret; 179062306a36Sopenharmony_ci} 179162306a36Sopenharmony_ci 179262306a36Sopenharmony_cistatic noinline_for_stack int ethtool_get_channels(struct net_device *dev, 179362306a36Sopenharmony_ci void __user *useraddr) 179462306a36Sopenharmony_ci{ 179562306a36Sopenharmony_ci struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS }; 179662306a36Sopenharmony_ci 179762306a36Sopenharmony_ci if (!dev->ethtool_ops->get_channels) 179862306a36Sopenharmony_ci return -EOPNOTSUPP; 179962306a36Sopenharmony_ci 180062306a36Sopenharmony_ci dev->ethtool_ops->get_channels(dev, &channels); 180162306a36Sopenharmony_ci 180262306a36Sopenharmony_ci if (copy_to_user(useraddr, &channels, sizeof(channels))) 180362306a36Sopenharmony_ci return -EFAULT; 180462306a36Sopenharmony_ci return 0; 180562306a36Sopenharmony_ci} 180662306a36Sopenharmony_ci 180762306a36Sopenharmony_cistatic noinline_for_stack int ethtool_set_channels(struct net_device *dev, 180862306a36Sopenharmony_ci void __user *useraddr) 180962306a36Sopenharmony_ci{ 181062306a36Sopenharmony_ci struct ethtool_channels channels, curr = { .cmd = ETHTOOL_GCHANNELS }; 181162306a36Sopenharmony_ci u16 from_channel, to_channel; 181262306a36Sopenharmony_ci u64 max_rxnfc_in_use; 181362306a36Sopenharmony_ci u32 max_rxfh_in_use; 181462306a36Sopenharmony_ci unsigned int i; 181562306a36Sopenharmony_ci int ret; 181662306a36Sopenharmony_ci 181762306a36Sopenharmony_ci if (!dev->ethtool_ops->set_channels || !dev->ethtool_ops->get_channels) 181862306a36Sopenharmony_ci return -EOPNOTSUPP; 181962306a36Sopenharmony_ci 182062306a36Sopenharmony_ci if (copy_from_user(&channels, useraddr, sizeof(channels))) 182162306a36Sopenharmony_ci return -EFAULT; 182262306a36Sopenharmony_ci 182362306a36Sopenharmony_ci dev->ethtool_ops->get_channels(dev, &curr); 182462306a36Sopenharmony_ci 182562306a36Sopenharmony_ci if (channels.rx_count == curr.rx_count && 182662306a36Sopenharmony_ci channels.tx_count == curr.tx_count && 182762306a36Sopenharmony_ci channels.combined_count == curr.combined_count && 182862306a36Sopenharmony_ci channels.other_count == curr.other_count) 182962306a36Sopenharmony_ci return 0; 183062306a36Sopenharmony_ci 183162306a36Sopenharmony_ci /* ensure new counts are within the maximums */ 183262306a36Sopenharmony_ci if (channels.rx_count > curr.max_rx || 183362306a36Sopenharmony_ci channels.tx_count > curr.max_tx || 183462306a36Sopenharmony_ci channels.combined_count > curr.max_combined || 183562306a36Sopenharmony_ci channels.other_count > curr.max_other) 183662306a36Sopenharmony_ci return -EINVAL; 183762306a36Sopenharmony_ci 183862306a36Sopenharmony_ci /* ensure there is at least one RX and one TX channel */ 183962306a36Sopenharmony_ci if (!channels.combined_count && 184062306a36Sopenharmony_ci (!channels.rx_count || !channels.tx_count)) 184162306a36Sopenharmony_ci return -EINVAL; 184262306a36Sopenharmony_ci 184362306a36Sopenharmony_ci /* ensure the new Rx count fits within the configured Rx flow 184462306a36Sopenharmony_ci * indirection table/rxnfc settings */ 184562306a36Sopenharmony_ci if (ethtool_get_max_rxnfc_channel(dev, &max_rxnfc_in_use)) 184662306a36Sopenharmony_ci max_rxnfc_in_use = 0; 184762306a36Sopenharmony_ci if (!netif_is_rxfh_configured(dev) || 184862306a36Sopenharmony_ci ethtool_get_max_rxfh_channel(dev, &max_rxfh_in_use)) 184962306a36Sopenharmony_ci max_rxfh_in_use = 0; 185062306a36Sopenharmony_ci if (channels.combined_count + channels.rx_count <= 185162306a36Sopenharmony_ci max_t(u64, max_rxnfc_in_use, max_rxfh_in_use)) 185262306a36Sopenharmony_ci return -EINVAL; 185362306a36Sopenharmony_ci 185462306a36Sopenharmony_ci /* Disabling channels, query zero-copy AF_XDP sockets */ 185562306a36Sopenharmony_ci from_channel = channels.combined_count + 185662306a36Sopenharmony_ci min(channels.rx_count, channels.tx_count); 185762306a36Sopenharmony_ci to_channel = curr.combined_count + max(curr.rx_count, curr.tx_count); 185862306a36Sopenharmony_ci for (i = from_channel; i < to_channel; i++) 185962306a36Sopenharmony_ci if (xsk_get_pool_from_qid(dev, i)) 186062306a36Sopenharmony_ci return -EINVAL; 186162306a36Sopenharmony_ci 186262306a36Sopenharmony_ci ret = dev->ethtool_ops->set_channels(dev, &channels); 186362306a36Sopenharmony_ci if (!ret) 186462306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF, NULL); 186562306a36Sopenharmony_ci return ret; 186662306a36Sopenharmony_ci} 186762306a36Sopenharmony_ci 186862306a36Sopenharmony_cistatic int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr) 186962306a36Sopenharmony_ci{ 187062306a36Sopenharmony_ci struct ethtool_pauseparam pauseparam = { .cmd = ETHTOOL_GPAUSEPARAM }; 187162306a36Sopenharmony_ci 187262306a36Sopenharmony_ci if (!dev->ethtool_ops->get_pauseparam) 187362306a36Sopenharmony_ci return -EOPNOTSUPP; 187462306a36Sopenharmony_ci 187562306a36Sopenharmony_ci dev->ethtool_ops->get_pauseparam(dev, &pauseparam); 187662306a36Sopenharmony_ci 187762306a36Sopenharmony_ci if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam))) 187862306a36Sopenharmony_ci return -EFAULT; 187962306a36Sopenharmony_ci return 0; 188062306a36Sopenharmony_ci} 188162306a36Sopenharmony_ci 188262306a36Sopenharmony_cistatic int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr) 188362306a36Sopenharmony_ci{ 188462306a36Sopenharmony_ci struct ethtool_pauseparam pauseparam; 188562306a36Sopenharmony_ci int ret; 188662306a36Sopenharmony_ci 188762306a36Sopenharmony_ci if (!dev->ethtool_ops->set_pauseparam) 188862306a36Sopenharmony_ci return -EOPNOTSUPP; 188962306a36Sopenharmony_ci 189062306a36Sopenharmony_ci if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam))) 189162306a36Sopenharmony_ci return -EFAULT; 189262306a36Sopenharmony_ci 189362306a36Sopenharmony_ci ret = dev->ethtool_ops->set_pauseparam(dev, &pauseparam); 189462306a36Sopenharmony_ci if (!ret) 189562306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_PAUSE_NTF, NULL); 189662306a36Sopenharmony_ci return ret; 189762306a36Sopenharmony_ci} 189862306a36Sopenharmony_ci 189962306a36Sopenharmony_cistatic int ethtool_self_test(struct net_device *dev, char __user *useraddr) 190062306a36Sopenharmony_ci{ 190162306a36Sopenharmony_ci struct ethtool_test test; 190262306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 190362306a36Sopenharmony_ci u64 *data; 190462306a36Sopenharmony_ci int ret, test_len; 190562306a36Sopenharmony_ci 190662306a36Sopenharmony_ci if (!ops->self_test || !ops->get_sset_count) 190762306a36Sopenharmony_ci return -EOPNOTSUPP; 190862306a36Sopenharmony_ci 190962306a36Sopenharmony_ci test_len = ops->get_sset_count(dev, ETH_SS_TEST); 191062306a36Sopenharmony_ci if (test_len < 0) 191162306a36Sopenharmony_ci return test_len; 191262306a36Sopenharmony_ci WARN_ON(test_len == 0); 191362306a36Sopenharmony_ci 191462306a36Sopenharmony_ci if (copy_from_user(&test, useraddr, sizeof(test))) 191562306a36Sopenharmony_ci return -EFAULT; 191662306a36Sopenharmony_ci 191762306a36Sopenharmony_ci test.len = test_len; 191862306a36Sopenharmony_ci data = kcalloc(test_len, sizeof(u64), GFP_USER); 191962306a36Sopenharmony_ci if (!data) 192062306a36Sopenharmony_ci return -ENOMEM; 192162306a36Sopenharmony_ci 192262306a36Sopenharmony_ci netif_testing_on(dev); 192362306a36Sopenharmony_ci ops->self_test(dev, &test, data); 192462306a36Sopenharmony_ci netif_testing_off(dev); 192562306a36Sopenharmony_ci 192662306a36Sopenharmony_ci ret = -EFAULT; 192762306a36Sopenharmony_ci if (copy_to_user(useraddr, &test, sizeof(test))) 192862306a36Sopenharmony_ci goto out; 192962306a36Sopenharmony_ci useraddr += sizeof(test); 193062306a36Sopenharmony_ci if (copy_to_user(useraddr, data, array_size(test.len, sizeof(u64)))) 193162306a36Sopenharmony_ci goto out; 193262306a36Sopenharmony_ci ret = 0; 193362306a36Sopenharmony_ci 193462306a36Sopenharmony_ci out: 193562306a36Sopenharmony_ci kfree(data); 193662306a36Sopenharmony_ci return ret; 193762306a36Sopenharmony_ci} 193862306a36Sopenharmony_ci 193962306a36Sopenharmony_cistatic int ethtool_get_strings(struct net_device *dev, void __user *useraddr) 194062306a36Sopenharmony_ci{ 194162306a36Sopenharmony_ci struct ethtool_gstrings gstrings; 194262306a36Sopenharmony_ci u8 *data; 194362306a36Sopenharmony_ci int ret; 194462306a36Sopenharmony_ci 194562306a36Sopenharmony_ci if (copy_from_user(&gstrings, useraddr, sizeof(gstrings))) 194662306a36Sopenharmony_ci return -EFAULT; 194762306a36Sopenharmony_ci 194862306a36Sopenharmony_ci ret = __ethtool_get_sset_count(dev, gstrings.string_set); 194962306a36Sopenharmony_ci if (ret < 0) 195062306a36Sopenharmony_ci return ret; 195162306a36Sopenharmony_ci if (ret > S32_MAX / ETH_GSTRING_LEN) 195262306a36Sopenharmony_ci return -ENOMEM; 195362306a36Sopenharmony_ci WARN_ON_ONCE(!ret); 195462306a36Sopenharmony_ci 195562306a36Sopenharmony_ci gstrings.len = ret; 195662306a36Sopenharmony_ci 195762306a36Sopenharmony_ci if (gstrings.len) { 195862306a36Sopenharmony_ci data = vzalloc(array_size(gstrings.len, ETH_GSTRING_LEN)); 195962306a36Sopenharmony_ci if (!data) 196062306a36Sopenharmony_ci return -ENOMEM; 196162306a36Sopenharmony_ci 196262306a36Sopenharmony_ci __ethtool_get_strings(dev, gstrings.string_set, data); 196362306a36Sopenharmony_ci } else { 196462306a36Sopenharmony_ci data = NULL; 196562306a36Sopenharmony_ci } 196662306a36Sopenharmony_ci 196762306a36Sopenharmony_ci ret = -EFAULT; 196862306a36Sopenharmony_ci if (copy_to_user(useraddr, &gstrings, sizeof(gstrings))) 196962306a36Sopenharmony_ci goto out; 197062306a36Sopenharmony_ci useraddr += sizeof(gstrings); 197162306a36Sopenharmony_ci if (gstrings.len && 197262306a36Sopenharmony_ci copy_to_user(useraddr, data, 197362306a36Sopenharmony_ci array_size(gstrings.len, ETH_GSTRING_LEN))) 197462306a36Sopenharmony_ci goto out; 197562306a36Sopenharmony_ci ret = 0; 197662306a36Sopenharmony_ci 197762306a36Sopenharmony_ciout: 197862306a36Sopenharmony_ci vfree(data); 197962306a36Sopenharmony_ci return ret; 198062306a36Sopenharmony_ci} 198162306a36Sopenharmony_ci 198262306a36Sopenharmony_ci__printf(2, 3) void ethtool_sprintf(u8 **data, const char *fmt, ...) 198362306a36Sopenharmony_ci{ 198462306a36Sopenharmony_ci va_list args; 198562306a36Sopenharmony_ci 198662306a36Sopenharmony_ci va_start(args, fmt); 198762306a36Sopenharmony_ci vsnprintf(*data, ETH_GSTRING_LEN, fmt, args); 198862306a36Sopenharmony_ci va_end(args); 198962306a36Sopenharmony_ci 199062306a36Sopenharmony_ci *data += ETH_GSTRING_LEN; 199162306a36Sopenharmony_ci} 199262306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_sprintf); 199362306a36Sopenharmony_ci 199462306a36Sopenharmony_cistatic int ethtool_phys_id(struct net_device *dev, void __user *useraddr) 199562306a36Sopenharmony_ci{ 199662306a36Sopenharmony_ci struct ethtool_value id; 199762306a36Sopenharmony_ci static bool busy; 199862306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 199962306a36Sopenharmony_ci netdevice_tracker dev_tracker; 200062306a36Sopenharmony_ci int rc; 200162306a36Sopenharmony_ci 200262306a36Sopenharmony_ci if (!ops->set_phys_id) 200362306a36Sopenharmony_ci return -EOPNOTSUPP; 200462306a36Sopenharmony_ci 200562306a36Sopenharmony_ci if (busy) 200662306a36Sopenharmony_ci return -EBUSY; 200762306a36Sopenharmony_ci 200862306a36Sopenharmony_ci if (copy_from_user(&id, useraddr, sizeof(id))) 200962306a36Sopenharmony_ci return -EFAULT; 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci rc = ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE); 201262306a36Sopenharmony_ci if (rc < 0) 201362306a36Sopenharmony_ci return rc; 201462306a36Sopenharmony_ci 201562306a36Sopenharmony_ci /* Drop the RTNL lock while waiting, but prevent reentry or 201662306a36Sopenharmony_ci * removal of the device. 201762306a36Sopenharmony_ci */ 201862306a36Sopenharmony_ci busy = true; 201962306a36Sopenharmony_ci netdev_hold(dev, &dev_tracker, GFP_KERNEL); 202062306a36Sopenharmony_ci rtnl_unlock(); 202162306a36Sopenharmony_ci 202262306a36Sopenharmony_ci if (rc == 0) { 202362306a36Sopenharmony_ci /* Driver will handle this itself */ 202462306a36Sopenharmony_ci schedule_timeout_interruptible( 202562306a36Sopenharmony_ci id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT); 202662306a36Sopenharmony_ci } else { 202762306a36Sopenharmony_ci /* Driver expects to be called at twice the frequency in rc */ 202862306a36Sopenharmony_ci int n = rc * 2, interval = HZ / n; 202962306a36Sopenharmony_ci u64 count = mul_u32_u32(n, id.data); 203062306a36Sopenharmony_ci u64 i = 0; 203162306a36Sopenharmony_ci 203262306a36Sopenharmony_ci do { 203362306a36Sopenharmony_ci rtnl_lock(); 203462306a36Sopenharmony_ci rc = ops->set_phys_id(dev, 203562306a36Sopenharmony_ci (i++ & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON); 203662306a36Sopenharmony_ci rtnl_unlock(); 203762306a36Sopenharmony_ci if (rc) 203862306a36Sopenharmony_ci break; 203962306a36Sopenharmony_ci schedule_timeout_interruptible(interval); 204062306a36Sopenharmony_ci } while (!signal_pending(current) && (!id.data || i < count)); 204162306a36Sopenharmony_ci } 204262306a36Sopenharmony_ci 204362306a36Sopenharmony_ci rtnl_lock(); 204462306a36Sopenharmony_ci netdev_put(dev, &dev_tracker); 204562306a36Sopenharmony_ci busy = false; 204662306a36Sopenharmony_ci 204762306a36Sopenharmony_ci (void) ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE); 204862306a36Sopenharmony_ci return rc; 204962306a36Sopenharmony_ci} 205062306a36Sopenharmony_ci 205162306a36Sopenharmony_cistatic int ethtool_get_stats(struct net_device *dev, void __user *useraddr) 205262306a36Sopenharmony_ci{ 205362306a36Sopenharmony_ci struct ethtool_stats stats; 205462306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 205562306a36Sopenharmony_ci u64 *data; 205662306a36Sopenharmony_ci int ret, n_stats; 205762306a36Sopenharmony_ci 205862306a36Sopenharmony_ci if (!ops->get_ethtool_stats || !ops->get_sset_count) 205962306a36Sopenharmony_ci return -EOPNOTSUPP; 206062306a36Sopenharmony_ci 206162306a36Sopenharmony_ci n_stats = ops->get_sset_count(dev, ETH_SS_STATS); 206262306a36Sopenharmony_ci if (n_stats < 0) 206362306a36Sopenharmony_ci return n_stats; 206462306a36Sopenharmony_ci if (n_stats > S32_MAX / sizeof(u64)) 206562306a36Sopenharmony_ci return -ENOMEM; 206662306a36Sopenharmony_ci WARN_ON_ONCE(!n_stats); 206762306a36Sopenharmony_ci if (copy_from_user(&stats, useraddr, sizeof(stats))) 206862306a36Sopenharmony_ci return -EFAULT; 206962306a36Sopenharmony_ci 207062306a36Sopenharmony_ci stats.n_stats = n_stats; 207162306a36Sopenharmony_ci 207262306a36Sopenharmony_ci if (n_stats) { 207362306a36Sopenharmony_ci data = vzalloc(array_size(n_stats, sizeof(u64))); 207462306a36Sopenharmony_ci if (!data) 207562306a36Sopenharmony_ci return -ENOMEM; 207662306a36Sopenharmony_ci ops->get_ethtool_stats(dev, &stats, data); 207762306a36Sopenharmony_ci } else { 207862306a36Sopenharmony_ci data = NULL; 207962306a36Sopenharmony_ci } 208062306a36Sopenharmony_ci 208162306a36Sopenharmony_ci ret = -EFAULT; 208262306a36Sopenharmony_ci if (copy_to_user(useraddr, &stats, sizeof(stats))) 208362306a36Sopenharmony_ci goto out; 208462306a36Sopenharmony_ci useraddr += sizeof(stats); 208562306a36Sopenharmony_ci if (n_stats && copy_to_user(useraddr, data, array_size(n_stats, sizeof(u64)))) 208662306a36Sopenharmony_ci goto out; 208762306a36Sopenharmony_ci ret = 0; 208862306a36Sopenharmony_ci 208962306a36Sopenharmony_ci out: 209062306a36Sopenharmony_ci vfree(data); 209162306a36Sopenharmony_ci return ret; 209262306a36Sopenharmony_ci} 209362306a36Sopenharmony_ci 209462306a36Sopenharmony_cistatic int ethtool_vzalloc_stats_array(int n_stats, u64 **data) 209562306a36Sopenharmony_ci{ 209662306a36Sopenharmony_ci if (n_stats < 0) 209762306a36Sopenharmony_ci return n_stats; 209862306a36Sopenharmony_ci if (n_stats > S32_MAX / sizeof(u64)) 209962306a36Sopenharmony_ci return -ENOMEM; 210062306a36Sopenharmony_ci if (WARN_ON_ONCE(!n_stats)) 210162306a36Sopenharmony_ci return -EOPNOTSUPP; 210262306a36Sopenharmony_ci 210362306a36Sopenharmony_ci *data = vzalloc(array_size(n_stats, sizeof(u64))); 210462306a36Sopenharmony_ci if (!*data) 210562306a36Sopenharmony_ci return -ENOMEM; 210662306a36Sopenharmony_ci 210762306a36Sopenharmony_ci return 0; 210862306a36Sopenharmony_ci} 210962306a36Sopenharmony_ci 211062306a36Sopenharmony_cistatic int ethtool_get_phy_stats_phydev(struct phy_device *phydev, 211162306a36Sopenharmony_ci struct ethtool_stats *stats, 211262306a36Sopenharmony_ci u64 **data) 211362306a36Sopenharmony_ci { 211462306a36Sopenharmony_ci const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops; 211562306a36Sopenharmony_ci int n_stats, ret; 211662306a36Sopenharmony_ci 211762306a36Sopenharmony_ci if (!phy_ops || !phy_ops->get_sset_count || !phy_ops->get_stats) 211862306a36Sopenharmony_ci return -EOPNOTSUPP; 211962306a36Sopenharmony_ci 212062306a36Sopenharmony_ci n_stats = phy_ops->get_sset_count(phydev); 212162306a36Sopenharmony_ci 212262306a36Sopenharmony_ci ret = ethtool_vzalloc_stats_array(n_stats, data); 212362306a36Sopenharmony_ci if (ret) 212462306a36Sopenharmony_ci return ret; 212562306a36Sopenharmony_ci 212662306a36Sopenharmony_ci stats->n_stats = n_stats; 212762306a36Sopenharmony_ci return phy_ops->get_stats(phydev, stats, *data); 212862306a36Sopenharmony_ci} 212962306a36Sopenharmony_ci 213062306a36Sopenharmony_cistatic int ethtool_get_phy_stats_ethtool(struct net_device *dev, 213162306a36Sopenharmony_ci struct ethtool_stats *stats, 213262306a36Sopenharmony_ci u64 **data) 213362306a36Sopenharmony_ci{ 213462306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 213562306a36Sopenharmony_ci int n_stats, ret; 213662306a36Sopenharmony_ci 213762306a36Sopenharmony_ci if (!ops || !ops->get_sset_count || ops->get_ethtool_phy_stats) 213862306a36Sopenharmony_ci return -EOPNOTSUPP; 213962306a36Sopenharmony_ci 214062306a36Sopenharmony_ci n_stats = ops->get_sset_count(dev, ETH_SS_PHY_STATS); 214162306a36Sopenharmony_ci 214262306a36Sopenharmony_ci ret = ethtool_vzalloc_stats_array(n_stats, data); 214362306a36Sopenharmony_ci if (ret) 214462306a36Sopenharmony_ci return ret; 214562306a36Sopenharmony_ci 214662306a36Sopenharmony_ci stats->n_stats = n_stats; 214762306a36Sopenharmony_ci ops->get_ethtool_phy_stats(dev, stats, *data); 214862306a36Sopenharmony_ci 214962306a36Sopenharmony_ci return 0; 215062306a36Sopenharmony_ci} 215162306a36Sopenharmony_ci 215262306a36Sopenharmony_cistatic int ethtool_get_phy_stats(struct net_device *dev, void __user *useraddr) 215362306a36Sopenharmony_ci{ 215462306a36Sopenharmony_ci struct phy_device *phydev = dev->phydev; 215562306a36Sopenharmony_ci struct ethtool_stats stats; 215662306a36Sopenharmony_ci u64 *data = NULL; 215762306a36Sopenharmony_ci int ret = -EOPNOTSUPP; 215862306a36Sopenharmony_ci 215962306a36Sopenharmony_ci if (copy_from_user(&stats, useraddr, sizeof(stats))) 216062306a36Sopenharmony_ci return -EFAULT; 216162306a36Sopenharmony_ci 216262306a36Sopenharmony_ci if (phydev) 216362306a36Sopenharmony_ci ret = ethtool_get_phy_stats_phydev(phydev, &stats, &data); 216462306a36Sopenharmony_ci 216562306a36Sopenharmony_ci if (ret == -EOPNOTSUPP) 216662306a36Sopenharmony_ci ret = ethtool_get_phy_stats_ethtool(dev, &stats, &data); 216762306a36Sopenharmony_ci 216862306a36Sopenharmony_ci if (ret) 216962306a36Sopenharmony_ci goto out; 217062306a36Sopenharmony_ci 217162306a36Sopenharmony_ci if (copy_to_user(useraddr, &stats, sizeof(stats))) { 217262306a36Sopenharmony_ci ret = -EFAULT; 217362306a36Sopenharmony_ci goto out; 217462306a36Sopenharmony_ci } 217562306a36Sopenharmony_ci 217662306a36Sopenharmony_ci useraddr += sizeof(stats); 217762306a36Sopenharmony_ci if (copy_to_user(useraddr, data, array_size(stats.n_stats, sizeof(u64)))) 217862306a36Sopenharmony_ci ret = -EFAULT; 217962306a36Sopenharmony_ci 218062306a36Sopenharmony_ci out: 218162306a36Sopenharmony_ci vfree(data); 218262306a36Sopenharmony_ci return ret; 218362306a36Sopenharmony_ci} 218462306a36Sopenharmony_ci 218562306a36Sopenharmony_cistatic int ethtool_get_perm_addr(struct net_device *dev, void __user *useraddr) 218662306a36Sopenharmony_ci{ 218762306a36Sopenharmony_ci struct ethtool_perm_addr epaddr; 218862306a36Sopenharmony_ci 218962306a36Sopenharmony_ci if (copy_from_user(&epaddr, useraddr, sizeof(epaddr))) 219062306a36Sopenharmony_ci return -EFAULT; 219162306a36Sopenharmony_ci 219262306a36Sopenharmony_ci if (epaddr.size < dev->addr_len) 219362306a36Sopenharmony_ci return -ETOOSMALL; 219462306a36Sopenharmony_ci epaddr.size = dev->addr_len; 219562306a36Sopenharmony_ci 219662306a36Sopenharmony_ci if (copy_to_user(useraddr, &epaddr, sizeof(epaddr))) 219762306a36Sopenharmony_ci return -EFAULT; 219862306a36Sopenharmony_ci useraddr += sizeof(epaddr); 219962306a36Sopenharmony_ci if (copy_to_user(useraddr, dev->perm_addr, epaddr.size)) 220062306a36Sopenharmony_ci return -EFAULT; 220162306a36Sopenharmony_ci return 0; 220262306a36Sopenharmony_ci} 220362306a36Sopenharmony_ci 220462306a36Sopenharmony_cistatic int ethtool_get_value(struct net_device *dev, char __user *useraddr, 220562306a36Sopenharmony_ci u32 cmd, u32 (*actor)(struct net_device *)) 220662306a36Sopenharmony_ci{ 220762306a36Sopenharmony_ci struct ethtool_value edata = { .cmd = cmd }; 220862306a36Sopenharmony_ci 220962306a36Sopenharmony_ci if (!actor) 221062306a36Sopenharmony_ci return -EOPNOTSUPP; 221162306a36Sopenharmony_ci 221262306a36Sopenharmony_ci edata.data = actor(dev); 221362306a36Sopenharmony_ci 221462306a36Sopenharmony_ci if (copy_to_user(useraddr, &edata, sizeof(edata))) 221562306a36Sopenharmony_ci return -EFAULT; 221662306a36Sopenharmony_ci return 0; 221762306a36Sopenharmony_ci} 221862306a36Sopenharmony_ci 221962306a36Sopenharmony_cistatic int ethtool_set_value_void(struct net_device *dev, char __user *useraddr, 222062306a36Sopenharmony_ci void (*actor)(struct net_device *, u32)) 222162306a36Sopenharmony_ci{ 222262306a36Sopenharmony_ci struct ethtool_value edata; 222362306a36Sopenharmony_ci 222462306a36Sopenharmony_ci if (!actor) 222562306a36Sopenharmony_ci return -EOPNOTSUPP; 222662306a36Sopenharmony_ci 222762306a36Sopenharmony_ci if (copy_from_user(&edata, useraddr, sizeof(edata))) 222862306a36Sopenharmony_ci return -EFAULT; 222962306a36Sopenharmony_ci 223062306a36Sopenharmony_ci actor(dev, edata.data); 223162306a36Sopenharmony_ci return 0; 223262306a36Sopenharmony_ci} 223362306a36Sopenharmony_ci 223462306a36Sopenharmony_cistatic int ethtool_set_value(struct net_device *dev, char __user *useraddr, 223562306a36Sopenharmony_ci int (*actor)(struct net_device *, u32)) 223662306a36Sopenharmony_ci{ 223762306a36Sopenharmony_ci struct ethtool_value edata; 223862306a36Sopenharmony_ci 223962306a36Sopenharmony_ci if (!actor) 224062306a36Sopenharmony_ci return -EOPNOTSUPP; 224162306a36Sopenharmony_ci 224262306a36Sopenharmony_ci if (copy_from_user(&edata, useraddr, sizeof(edata))) 224362306a36Sopenharmony_ci return -EFAULT; 224462306a36Sopenharmony_ci 224562306a36Sopenharmony_ci return actor(dev, edata.data); 224662306a36Sopenharmony_ci} 224762306a36Sopenharmony_ci 224862306a36Sopenharmony_cistatic int 224962306a36Sopenharmony_ciethtool_flash_device(struct net_device *dev, struct ethtool_devlink_compat *req) 225062306a36Sopenharmony_ci{ 225162306a36Sopenharmony_ci if (!dev->ethtool_ops->flash_device) { 225262306a36Sopenharmony_ci req->devlink = netdev_to_devlink_get(dev); 225362306a36Sopenharmony_ci return 0; 225462306a36Sopenharmony_ci } 225562306a36Sopenharmony_ci 225662306a36Sopenharmony_ci return dev->ethtool_ops->flash_device(dev, &req->efl); 225762306a36Sopenharmony_ci} 225862306a36Sopenharmony_ci 225962306a36Sopenharmony_cistatic int ethtool_set_dump(struct net_device *dev, 226062306a36Sopenharmony_ci void __user *useraddr) 226162306a36Sopenharmony_ci{ 226262306a36Sopenharmony_ci struct ethtool_dump dump; 226362306a36Sopenharmony_ci 226462306a36Sopenharmony_ci if (!dev->ethtool_ops->set_dump) 226562306a36Sopenharmony_ci return -EOPNOTSUPP; 226662306a36Sopenharmony_ci 226762306a36Sopenharmony_ci if (copy_from_user(&dump, useraddr, sizeof(dump))) 226862306a36Sopenharmony_ci return -EFAULT; 226962306a36Sopenharmony_ci 227062306a36Sopenharmony_ci return dev->ethtool_ops->set_dump(dev, &dump); 227162306a36Sopenharmony_ci} 227262306a36Sopenharmony_ci 227362306a36Sopenharmony_cistatic int ethtool_get_dump_flag(struct net_device *dev, 227462306a36Sopenharmony_ci void __user *useraddr) 227562306a36Sopenharmony_ci{ 227662306a36Sopenharmony_ci int ret; 227762306a36Sopenharmony_ci struct ethtool_dump dump; 227862306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 227962306a36Sopenharmony_ci 228062306a36Sopenharmony_ci if (!ops->get_dump_flag) 228162306a36Sopenharmony_ci return -EOPNOTSUPP; 228262306a36Sopenharmony_ci 228362306a36Sopenharmony_ci if (copy_from_user(&dump, useraddr, sizeof(dump))) 228462306a36Sopenharmony_ci return -EFAULT; 228562306a36Sopenharmony_ci 228662306a36Sopenharmony_ci ret = ops->get_dump_flag(dev, &dump); 228762306a36Sopenharmony_ci if (ret) 228862306a36Sopenharmony_ci return ret; 228962306a36Sopenharmony_ci 229062306a36Sopenharmony_ci if (copy_to_user(useraddr, &dump, sizeof(dump))) 229162306a36Sopenharmony_ci return -EFAULT; 229262306a36Sopenharmony_ci return 0; 229362306a36Sopenharmony_ci} 229462306a36Sopenharmony_ci 229562306a36Sopenharmony_cistatic int ethtool_get_dump_data(struct net_device *dev, 229662306a36Sopenharmony_ci void __user *useraddr) 229762306a36Sopenharmony_ci{ 229862306a36Sopenharmony_ci int ret; 229962306a36Sopenharmony_ci __u32 len; 230062306a36Sopenharmony_ci struct ethtool_dump dump, tmp; 230162306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 230262306a36Sopenharmony_ci void *data = NULL; 230362306a36Sopenharmony_ci 230462306a36Sopenharmony_ci if (!ops->get_dump_data || !ops->get_dump_flag) 230562306a36Sopenharmony_ci return -EOPNOTSUPP; 230662306a36Sopenharmony_ci 230762306a36Sopenharmony_ci if (copy_from_user(&dump, useraddr, sizeof(dump))) 230862306a36Sopenharmony_ci return -EFAULT; 230962306a36Sopenharmony_ci 231062306a36Sopenharmony_ci memset(&tmp, 0, sizeof(tmp)); 231162306a36Sopenharmony_ci tmp.cmd = ETHTOOL_GET_DUMP_FLAG; 231262306a36Sopenharmony_ci ret = ops->get_dump_flag(dev, &tmp); 231362306a36Sopenharmony_ci if (ret) 231462306a36Sopenharmony_ci return ret; 231562306a36Sopenharmony_ci 231662306a36Sopenharmony_ci len = min(tmp.len, dump.len); 231762306a36Sopenharmony_ci if (!len) 231862306a36Sopenharmony_ci return -EFAULT; 231962306a36Sopenharmony_ci 232062306a36Sopenharmony_ci /* Don't ever let the driver think there's more space available 232162306a36Sopenharmony_ci * than it requested with .get_dump_flag(). 232262306a36Sopenharmony_ci */ 232362306a36Sopenharmony_ci dump.len = len; 232462306a36Sopenharmony_ci 232562306a36Sopenharmony_ci /* Always allocate enough space to hold the whole thing so that the 232662306a36Sopenharmony_ci * driver does not need to check the length and bother with partial 232762306a36Sopenharmony_ci * dumping. 232862306a36Sopenharmony_ci */ 232962306a36Sopenharmony_ci data = vzalloc(tmp.len); 233062306a36Sopenharmony_ci if (!data) 233162306a36Sopenharmony_ci return -ENOMEM; 233262306a36Sopenharmony_ci ret = ops->get_dump_data(dev, &dump, data); 233362306a36Sopenharmony_ci if (ret) 233462306a36Sopenharmony_ci goto out; 233562306a36Sopenharmony_ci 233662306a36Sopenharmony_ci /* There are two sane possibilities: 233762306a36Sopenharmony_ci * 1. The driver's .get_dump_data() does not touch dump.len. 233862306a36Sopenharmony_ci * 2. Or it may set dump.len to how much it really writes, which 233962306a36Sopenharmony_ci * should be tmp.len (or len if it can do a partial dump). 234062306a36Sopenharmony_ci * In any case respond to userspace with the actual length of data 234162306a36Sopenharmony_ci * it's receiving. 234262306a36Sopenharmony_ci */ 234362306a36Sopenharmony_ci WARN_ON(dump.len != len && dump.len != tmp.len); 234462306a36Sopenharmony_ci dump.len = len; 234562306a36Sopenharmony_ci 234662306a36Sopenharmony_ci if (copy_to_user(useraddr, &dump, sizeof(dump))) { 234762306a36Sopenharmony_ci ret = -EFAULT; 234862306a36Sopenharmony_ci goto out; 234962306a36Sopenharmony_ci } 235062306a36Sopenharmony_ci useraddr += offsetof(struct ethtool_dump, data); 235162306a36Sopenharmony_ci if (copy_to_user(useraddr, data, len)) 235262306a36Sopenharmony_ci ret = -EFAULT; 235362306a36Sopenharmony_ciout: 235462306a36Sopenharmony_ci vfree(data); 235562306a36Sopenharmony_ci return ret; 235662306a36Sopenharmony_ci} 235762306a36Sopenharmony_ci 235862306a36Sopenharmony_cistatic int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr) 235962306a36Sopenharmony_ci{ 236062306a36Sopenharmony_ci struct ethtool_ts_info info; 236162306a36Sopenharmony_ci int err; 236262306a36Sopenharmony_ci 236362306a36Sopenharmony_ci err = __ethtool_get_ts_info(dev, &info); 236462306a36Sopenharmony_ci if (err) 236562306a36Sopenharmony_ci return err; 236662306a36Sopenharmony_ci 236762306a36Sopenharmony_ci if (copy_to_user(useraddr, &info, sizeof(info))) 236862306a36Sopenharmony_ci return -EFAULT; 236962306a36Sopenharmony_ci 237062306a36Sopenharmony_ci return 0; 237162306a36Sopenharmony_ci} 237262306a36Sopenharmony_ci 237362306a36Sopenharmony_ciint ethtool_get_module_info_call(struct net_device *dev, 237462306a36Sopenharmony_ci struct ethtool_modinfo *modinfo) 237562306a36Sopenharmony_ci{ 237662306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 237762306a36Sopenharmony_ci struct phy_device *phydev = dev->phydev; 237862306a36Sopenharmony_ci 237962306a36Sopenharmony_ci if (dev->sfp_bus) 238062306a36Sopenharmony_ci return sfp_get_module_info(dev->sfp_bus, modinfo); 238162306a36Sopenharmony_ci 238262306a36Sopenharmony_ci if (phydev && phydev->drv && phydev->drv->module_info) 238362306a36Sopenharmony_ci return phydev->drv->module_info(phydev, modinfo); 238462306a36Sopenharmony_ci 238562306a36Sopenharmony_ci if (ops->get_module_info) 238662306a36Sopenharmony_ci return ops->get_module_info(dev, modinfo); 238762306a36Sopenharmony_ci 238862306a36Sopenharmony_ci return -EOPNOTSUPP; 238962306a36Sopenharmony_ci} 239062306a36Sopenharmony_ci 239162306a36Sopenharmony_cistatic int ethtool_get_module_info(struct net_device *dev, 239262306a36Sopenharmony_ci void __user *useraddr) 239362306a36Sopenharmony_ci{ 239462306a36Sopenharmony_ci int ret; 239562306a36Sopenharmony_ci struct ethtool_modinfo modinfo; 239662306a36Sopenharmony_ci 239762306a36Sopenharmony_ci if (copy_from_user(&modinfo, useraddr, sizeof(modinfo))) 239862306a36Sopenharmony_ci return -EFAULT; 239962306a36Sopenharmony_ci 240062306a36Sopenharmony_ci ret = ethtool_get_module_info_call(dev, &modinfo); 240162306a36Sopenharmony_ci if (ret) 240262306a36Sopenharmony_ci return ret; 240362306a36Sopenharmony_ci 240462306a36Sopenharmony_ci if (copy_to_user(useraddr, &modinfo, sizeof(modinfo))) 240562306a36Sopenharmony_ci return -EFAULT; 240662306a36Sopenharmony_ci 240762306a36Sopenharmony_ci return 0; 240862306a36Sopenharmony_ci} 240962306a36Sopenharmony_ci 241062306a36Sopenharmony_ciint ethtool_get_module_eeprom_call(struct net_device *dev, 241162306a36Sopenharmony_ci struct ethtool_eeprom *ee, u8 *data) 241262306a36Sopenharmony_ci{ 241362306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 241462306a36Sopenharmony_ci struct phy_device *phydev = dev->phydev; 241562306a36Sopenharmony_ci 241662306a36Sopenharmony_ci if (dev->sfp_bus) 241762306a36Sopenharmony_ci return sfp_get_module_eeprom(dev->sfp_bus, ee, data); 241862306a36Sopenharmony_ci 241962306a36Sopenharmony_ci if (phydev && phydev->drv && phydev->drv->module_eeprom) 242062306a36Sopenharmony_ci return phydev->drv->module_eeprom(phydev, ee, data); 242162306a36Sopenharmony_ci 242262306a36Sopenharmony_ci if (ops->get_module_eeprom) 242362306a36Sopenharmony_ci return ops->get_module_eeprom(dev, ee, data); 242462306a36Sopenharmony_ci 242562306a36Sopenharmony_ci return -EOPNOTSUPP; 242662306a36Sopenharmony_ci} 242762306a36Sopenharmony_ci 242862306a36Sopenharmony_cistatic int ethtool_get_module_eeprom(struct net_device *dev, 242962306a36Sopenharmony_ci void __user *useraddr) 243062306a36Sopenharmony_ci{ 243162306a36Sopenharmony_ci int ret; 243262306a36Sopenharmony_ci struct ethtool_modinfo modinfo; 243362306a36Sopenharmony_ci 243462306a36Sopenharmony_ci ret = ethtool_get_module_info_call(dev, &modinfo); 243562306a36Sopenharmony_ci if (ret) 243662306a36Sopenharmony_ci return ret; 243762306a36Sopenharmony_ci 243862306a36Sopenharmony_ci return ethtool_get_any_eeprom(dev, useraddr, 243962306a36Sopenharmony_ci ethtool_get_module_eeprom_call, 244062306a36Sopenharmony_ci modinfo.eeprom_len); 244162306a36Sopenharmony_ci} 244262306a36Sopenharmony_ci 244362306a36Sopenharmony_cistatic int ethtool_tunable_valid(const struct ethtool_tunable *tuna) 244462306a36Sopenharmony_ci{ 244562306a36Sopenharmony_ci switch (tuna->id) { 244662306a36Sopenharmony_ci case ETHTOOL_RX_COPYBREAK: 244762306a36Sopenharmony_ci case ETHTOOL_TX_COPYBREAK: 244862306a36Sopenharmony_ci case ETHTOOL_TX_COPYBREAK_BUF_SIZE: 244962306a36Sopenharmony_ci if (tuna->len != sizeof(u32) || 245062306a36Sopenharmony_ci tuna->type_id != ETHTOOL_TUNABLE_U32) 245162306a36Sopenharmony_ci return -EINVAL; 245262306a36Sopenharmony_ci break; 245362306a36Sopenharmony_ci case ETHTOOL_PFC_PREVENTION_TOUT: 245462306a36Sopenharmony_ci if (tuna->len != sizeof(u16) || 245562306a36Sopenharmony_ci tuna->type_id != ETHTOOL_TUNABLE_U16) 245662306a36Sopenharmony_ci return -EINVAL; 245762306a36Sopenharmony_ci break; 245862306a36Sopenharmony_ci default: 245962306a36Sopenharmony_ci return -EINVAL; 246062306a36Sopenharmony_ci } 246162306a36Sopenharmony_ci 246262306a36Sopenharmony_ci return 0; 246362306a36Sopenharmony_ci} 246462306a36Sopenharmony_ci 246562306a36Sopenharmony_cistatic int ethtool_get_tunable(struct net_device *dev, void __user *useraddr) 246662306a36Sopenharmony_ci{ 246762306a36Sopenharmony_ci int ret; 246862306a36Sopenharmony_ci struct ethtool_tunable tuna; 246962306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 247062306a36Sopenharmony_ci void *data; 247162306a36Sopenharmony_ci 247262306a36Sopenharmony_ci if (!ops->get_tunable) 247362306a36Sopenharmony_ci return -EOPNOTSUPP; 247462306a36Sopenharmony_ci if (copy_from_user(&tuna, useraddr, sizeof(tuna))) 247562306a36Sopenharmony_ci return -EFAULT; 247662306a36Sopenharmony_ci ret = ethtool_tunable_valid(&tuna); 247762306a36Sopenharmony_ci if (ret) 247862306a36Sopenharmony_ci return ret; 247962306a36Sopenharmony_ci data = kzalloc(tuna.len, GFP_USER); 248062306a36Sopenharmony_ci if (!data) 248162306a36Sopenharmony_ci return -ENOMEM; 248262306a36Sopenharmony_ci ret = ops->get_tunable(dev, &tuna, data); 248362306a36Sopenharmony_ci if (ret) 248462306a36Sopenharmony_ci goto out; 248562306a36Sopenharmony_ci useraddr += sizeof(tuna); 248662306a36Sopenharmony_ci ret = -EFAULT; 248762306a36Sopenharmony_ci if (copy_to_user(useraddr, data, tuna.len)) 248862306a36Sopenharmony_ci goto out; 248962306a36Sopenharmony_ci ret = 0; 249062306a36Sopenharmony_ci 249162306a36Sopenharmony_ciout: 249262306a36Sopenharmony_ci kfree(data); 249362306a36Sopenharmony_ci return ret; 249462306a36Sopenharmony_ci} 249562306a36Sopenharmony_ci 249662306a36Sopenharmony_cistatic int ethtool_set_tunable(struct net_device *dev, void __user *useraddr) 249762306a36Sopenharmony_ci{ 249862306a36Sopenharmony_ci int ret; 249962306a36Sopenharmony_ci struct ethtool_tunable tuna; 250062306a36Sopenharmony_ci const struct ethtool_ops *ops = dev->ethtool_ops; 250162306a36Sopenharmony_ci void *data; 250262306a36Sopenharmony_ci 250362306a36Sopenharmony_ci if (!ops->set_tunable) 250462306a36Sopenharmony_ci return -EOPNOTSUPP; 250562306a36Sopenharmony_ci if (copy_from_user(&tuna, useraddr, sizeof(tuna))) 250662306a36Sopenharmony_ci return -EFAULT; 250762306a36Sopenharmony_ci ret = ethtool_tunable_valid(&tuna); 250862306a36Sopenharmony_ci if (ret) 250962306a36Sopenharmony_ci return ret; 251062306a36Sopenharmony_ci useraddr += sizeof(tuna); 251162306a36Sopenharmony_ci data = memdup_user(useraddr, tuna.len); 251262306a36Sopenharmony_ci if (IS_ERR(data)) 251362306a36Sopenharmony_ci return PTR_ERR(data); 251462306a36Sopenharmony_ci ret = ops->set_tunable(dev, &tuna, data); 251562306a36Sopenharmony_ci 251662306a36Sopenharmony_ci kfree(data); 251762306a36Sopenharmony_ci return ret; 251862306a36Sopenharmony_ci} 251962306a36Sopenharmony_ci 252062306a36Sopenharmony_cistatic noinline_for_stack int 252162306a36Sopenharmony_ciethtool_get_per_queue_coalesce(struct net_device *dev, 252262306a36Sopenharmony_ci void __user *useraddr, 252362306a36Sopenharmony_ci struct ethtool_per_queue_op *per_queue_opt) 252462306a36Sopenharmony_ci{ 252562306a36Sopenharmony_ci u32 bit; 252662306a36Sopenharmony_ci int ret; 252762306a36Sopenharmony_ci DECLARE_BITMAP(queue_mask, MAX_NUM_QUEUE); 252862306a36Sopenharmony_ci 252962306a36Sopenharmony_ci if (!dev->ethtool_ops->get_per_queue_coalesce) 253062306a36Sopenharmony_ci return -EOPNOTSUPP; 253162306a36Sopenharmony_ci 253262306a36Sopenharmony_ci useraddr += sizeof(*per_queue_opt); 253362306a36Sopenharmony_ci 253462306a36Sopenharmony_ci bitmap_from_arr32(queue_mask, per_queue_opt->queue_mask, 253562306a36Sopenharmony_ci MAX_NUM_QUEUE); 253662306a36Sopenharmony_ci 253762306a36Sopenharmony_ci for_each_set_bit(bit, queue_mask, MAX_NUM_QUEUE) { 253862306a36Sopenharmony_ci struct ethtool_coalesce coalesce = { .cmd = ETHTOOL_GCOALESCE }; 253962306a36Sopenharmony_ci 254062306a36Sopenharmony_ci ret = dev->ethtool_ops->get_per_queue_coalesce(dev, bit, &coalesce); 254162306a36Sopenharmony_ci if (ret != 0) 254262306a36Sopenharmony_ci return ret; 254362306a36Sopenharmony_ci if (copy_to_user(useraddr, &coalesce, sizeof(coalesce))) 254462306a36Sopenharmony_ci return -EFAULT; 254562306a36Sopenharmony_ci useraddr += sizeof(coalesce); 254662306a36Sopenharmony_ci } 254762306a36Sopenharmony_ci 254862306a36Sopenharmony_ci return 0; 254962306a36Sopenharmony_ci} 255062306a36Sopenharmony_ci 255162306a36Sopenharmony_cistatic noinline_for_stack int 255262306a36Sopenharmony_ciethtool_set_per_queue_coalesce(struct net_device *dev, 255362306a36Sopenharmony_ci void __user *useraddr, 255462306a36Sopenharmony_ci struct ethtool_per_queue_op *per_queue_opt) 255562306a36Sopenharmony_ci{ 255662306a36Sopenharmony_ci u32 bit; 255762306a36Sopenharmony_ci int i, ret = 0; 255862306a36Sopenharmony_ci int n_queue; 255962306a36Sopenharmony_ci struct ethtool_coalesce *backup = NULL, *tmp = NULL; 256062306a36Sopenharmony_ci DECLARE_BITMAP(queue_mask, MAX_NUM_QUEUE); 256162306a36Sopenharmony_ci 256262306a36Sopenharmony_ci if ((!dev->ethtool_ops->set_per_queue_coalesce) || 256362306a36Sopenharmony_ci (!dev->ethtool_ops->get_per_queue_coalesce)) 256462306a36Sopenharmony_ci return -EOPNOTSUPP; 256562306a36Sopenharmony_ci 256662306a36Sopenharmony_ci useraddr += sizeof(*per_queue_opt); 256762306a36Sopenharmony_ci 256862306a36Sopenharmony_ci bitmap_from_arr32(queue_mask, per_queue_opt->queue_mask, MAX_NUM_QUEUE); 256962306a36Sopenharmony_ci n_queue = bitmap_weight(queue_mask, MAX_NUM_QUEUE); 257062306a36Sopenharmony_ci tmp = backup = kmalloc_array(n_queue, sizeof(*backup), GFP_KERNEL); 257162306a36Sopenharmony_ci if (!backup) 257262306a36Sopenharmony_ci return -ENOMEM; 257362306a36Sopenharmony_ci 257462306a36Sopenharmony_ci for_each_set_bit(bit, queue_mask, MAX_NUM_QUEUE) { 257562306a36Sopenharmony_ci struct ethtool_coalesce coalesce; 257662306a36Sopenharmony_ci 257762306a36Sopenharmony_ci ret = dev->ethtool_ops->get_per_queue_coalesce(dev, bit, tmp); 257862306a36Sopenharmony_ci if (ret != 0) 257962306a36Sopenharmony_ci goto roll_back; 258062306a36Sopenharmony_ci 258162306a36Sopenharmony_ci tmp++; 258262306a36Sopenharmony_ci 258362306a36Sopenharmony_ci if (copy_from_user(&coalesce, useraddr, sizeof(coalesce))) { 258462306a36Sopenharmony_ci ret = -EFAULT; 258562306a36Sopenharmony_ci goto roll_back; 258662306a36Sopenharmony_ci } 258762306a36Sopenharmony_ci 258862306a36Sopenharmony_ci if (!ethtool_set_coalesce_supported(dev, &coalesce)) { 258962306a36Sopenharmony_ci ret = -EOPNOTSUPP; 259062306a36Sopenharmony_ci goto roll_back; 259162306a36Sopenharmony_ci } 259262306a36Sopenharmony_ci 259362306a36Sopenharmony_ci ret = dev->ethtool_ops->set_per_queue_coalesce(dev, bit, &coalesce); 259462306a36Sopenharmony_ci if (ret != 0) 259562306a36Sopenharmony_ci goto roll_back; 259662306a36Sopenharmony_ci 259762306a36Sopenharmony_ci useraddr += sizeof(coalesce); 259862306a36Sopenharmony_ci } 259962306a36Sopenharmony_ci 260062306a36Sopenharmony_ciroll_back: 260162306a36Sopenharmony_ci if (ret != 0) { 260262306a36Sopenharmony_ci tmp = backup; 260362306a36Sopenharmony_ci for_each_set_bit(i, queue_mask, bit) { 260462306a36Sopenharmony_ci dev->ethtool_ops->set_per_queue_coalesce(dev, i, tmp); 260562306a36Sopenharmony_ci tmp++; 260662306a36Sopenharmony_ci } 260762306a36Sopenharmony_ci } 260862306a36Sopenharmony_ci kfree(backup); 260962306a36Sopenharmony_ci 261062306a36Sopenharmony_ci return ret; 261162306a36Sopenharmony_ci} 261262306a36Sopenharmony_ci 261362306a36Sopenharmony_cistatic int noinline_for_stack ethtool_set_per_queue(struct net_device *dev, 261462306a36Sopenharmony_ci void __user *useraddr, u32 sub_cmd) 261562306a36Sopenharmony_ci{ 261662306a36Sopenharmony_ci struct ethtool_per_queue_op per_queue_opt; 261762306a36Sopenharmony_ci 261862306a36Sopenharmony_ci if (copy_from_user(&per_queue_opt, useraddr, sizeof(per_queue_opt))) 261962306a36Sopenharmony_ci return -EFAULT; 262062306a36Sopenharmony_ci 262162306a36Sopenharmony_ci if (per_queue_opt.sub_command != sub_cmd) 262262306a36Sopenharmony_ci return -EINVAL; 262362306a36Sopenharmony_ci 262462306a36Sopenharmony_ci switch (per_queue_opt.sub_command) { 262562306a36Sopenharmony_ci case ETHTOOL_GCOALESCE: 262662306a36Sopenharmony_ci return ethtool_get_per_queue_coalesce(dev, useraddr, &per_queue_opt); 262762306a36Sopenharmony_ci case ETHTOOL_SCOALESCE: 262862306a36Sopenharmony_ci return ethtool_set_per_queue_coalesce(dev, useraddr, &per_queue_opt); 262962306a36Sopenharmony_ci default: 263062306a36Sopenharmony_ci return -EOPNOTSUPP; 263162306a36Sopenharmony_ci } 263262306a36Sopenharmony_ci} 263362306a36Sopenharmony_ci 263462306a36Sopenharmony_cistatic int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna) 263562306a36Sopenharmony_ci{ 263662306a36Sopenharmony_ci switch (tuna->id) { 263762306a36Sopenharmony_ci case ETHTOOL_PHY_DOWNSHIFT: 263862306a36Sopenharmony_ci case ETHTOOL_PHY_FAST_LINK_DOWN: 263962306a36Sopenharmony_ci if (tuna->len != sizeof(u8) || 264062306a36Sopenharmony_ci tuna->type_id != ETHTOOL_TUNABLE_U8) 264162306a36Sopenharmony_ci return -EINVAL; 264262306a36Sopenharmony_ci break; 264362306a36Sopenharmony_ci case ETHTOOL_PHY_EDPD: 264462306a36Sopenharmony_ci if (tuna->len != sizeof(u16) || 264562306a36Sopenharmony_ci tuna->type_id != ETHTOOL_TUNABLE_U16) 264662306a36Sopenharmony_ci return -EINVAL; 264762306a36Sopenharmony_ci break; 264862306a36Sopenharmony_ci default: 264962306a36Sopenharmony_ci return -EINVAL; 265062306a36Sopenharmony_ci } 265162306a36Sopenharmony_ci 265262306a36Sopenharmony_ci return 0; 265362306a36Sopenharmony_ci} 265462306a36Sopenharmony_ci 265562306a36Sopenharmony_cistatic int get_phy_tunable(struct net_device *dev, void __user *useraddr) 265662306a36Sopenharmony_ci{ 265762306a36Sopenharmony_ci struct phy_device *phydev = dev->phydev; 265862306a36Sopenharmony_ci struct ethtool_tunable tuna; 265962306a36Sopenharmony_ci bool phy_drv_tunable; 266062306a36Sopenharmony_ci void *data; 266162306a36Sopenharmony_ci int ret; 266262306a36Sopenharmony_ci 266362306a36Sopenharmony_ci phy_drv_tunable = phydev && phydev->drv && phydev->drv->get_tunable; 266462306a36Sopenharmony_ci if (!phy_drv_tunable && !dev->ethtool_ops->get_phy_tunable) 266562306a36Sopenharmony_ci return -EOPNOTSUPP; 266662306a36Sopenharmony_ci if (copy_from_user(&tuna, useraddr, sizeof(tuna))) 266762306a36Sopenharmony_ci return -EFAULT; 266862306a36Sopenharmony_ci ret = ethtool_phy_tunable_valid(&tuna); 266962306a36Sopenharmony_ci if (ret) 267062306a36Sopenharmony_ci return ret; 267162306a36Sopenharmony_ci data = kzalloc(tuna.len, GFP_USER); 267262306a36Sopenharmony_ci if (!data) 267362306a36Sopenharmony_ci return -ENOMEM; 267462306a36Sopenharmony_ci if (phy_drv_tunable) { 267562306a36Sopenharmony_ci mutex_lock(&phydev->lock); 267662306a36Sopenharmony_ci ret = phydev->drv->get_tunable(phydev, &tuna, data); 267762306a36Sopenharmony_ci mutex_unlock(&phydev->lock); 267862306a36Sopenharmony_ci } else { 267962306a36Sopenharmony_ci ret = dev->ethtool_ops->get_phy_tunable(dev, &tuna, data); 268062306a36Sopenharmony_ci } 268162306a36Sopenharmony_ci if (ret) 268262306a36Sopenharmony_ci goto out; 268362306a36Sopenharmony_ci useraddr += sizeof(tuna); 268462306a36Sopenharmony_ci ret = -EFAULT; 268562306a36Sopenharmony_ci if (copy_to_user(useraddr, data, tuna.len)) 268662306a36Sopenharmony_ci goto out; 268762306a36Sopenharmony_ci ret = 0; 268862306a36Sopenharmony_ci 268962306a36Sopenharmony_ciout: 269062306a36Sopenharmony_ci kfree(data); 269162306a36Sopenharmony_ci return ret; 269262306a36Sopenharmony_ci} 269362306a36Sopenharmony_ci 269462306a36Sopenharmony_cistatic int set_phy_tunable(struct net_device *dev, void __user *useraddr) 269562306a36Sopenharmony_ci{ 269662306a36Sopenharmony_ci struct phy_device *phydev = dev->phydev; 269762306a36Sopenharmony_ci struct ethtool_tunable tuna; 269862306a36Sopenharmony_ci bool phy_drv_tunable; 269962306a36Sopenharmony_ci void *data; 270062306a36Sopenharmony_ci int ret; 270162306a36Sopenharmony_ci 270262306a36Sopenharmony_ci phy_drv_tunable = phydev && phydev->drv && phydev->drv->get_tunable; 270362306a36Sopenharmony_ci if (!phy_drv_tunable && !dev->ethtool_ops->set_phy_tunable) 270462306a36Sopenharmony_ci return -EOPNOTSUPP; 270562306a36Sopenharmony_ci if (copy_from_user(&tuna, useraddr, sizeof(tuna))) 270662306a36Sopenharmony_ci return -EFAULT; 270762306a36Sopenharmony_ci ret = ethtool_phy_tunable_valid(&tuna); 270862306a36Sopenharmony_ci if (ret) 270962306a36Sopenharmony_ci return ret; 271062306a36Sopenharmony_ci useraddr += sizeof(tuna); 271162306a36Sopenharmony_ci data = memdup_user(useraddr, tuna.len); 271262306a36Sopenharmony_ci if (IS_ERR(data)) 271362306a36Sopenharmony_ci return PTR_ERR(data); 271462306a36Sopenharmony_ci if (phy_drv_tunable) { 271562306a36Sopenharmony_ci mutex_lock(&phydev->lock); 271662306a36Sopenharmony_ci ret = phydev->drv->set_tunable(phydev, &tuna, data); 271762306a36Sopenharmony_ci mutex_unlock(&phydev->lock); 271862306a36Sopenharmony_ci } else { 271962306a36Sopenharmony_ci ret = dev->ethtool_ops->set_phy_tunable(dev, &tuna, data); 272062306a36Sopenharmony_ci } 272162306a36Sopenharmony_ci 272262306a36Sopenharmony_ci kfree(data); 272362306a36Sopenharmony_ci return ret; 272462306a36Sopenharmony_ci} 272562306a36Sopenharmony_ci 272662306a36Sopenharmony_cistatic int ethtool_get_fecparam(struct net_device *dev, void __user *useraddr) 272762306a36Sopenharmony_ci{ 272862306a36Sopenharmony_ci struct ethtool_fecparam fecparam = { .cmd = ETHTOOL_GFECPARAM }; 272962306a36Sopenharmony_ci int rc; 273062306a36Sopenharmony_ci 273162306a36Sopenharmony_ci if (!dev->ethtool_ops->get_fecparam) 273262306a36Sopenharmony_ci return -EOPNOTSUPP; 273362306a36Sopenharmony_ci 273462306a36Sopenharmony_ci rc = dev->ethtool_ops->get_fecparam(dev, &fecparam); 273562306a36Sopenharmony_ci if (rc) 273662306a36Sopenharmony_ci return rc; 273762306a36Sopenharmony_ci 273862306a36Sopenharmony_ci if (WARN_ON_ONCE(fecparam.reserved)) 273962306a36Sopenharmony_ci fecparam.reserved = 0; 274062306a36Sopenharmony_ci 274162306a36Sopenharmony_ci if (copy_to_user(useraddr, &fecparam, sizeof(fecparam))) 274262306a36Sopenharmony_ci return -EFAULT; 274362306a36Sopenharmony_ci return 0; 274462306a36Sopenharmony_ci} 274562306a36Sopenharmony_ci 274662306a36Sopenharmony_cistatic int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr) 274762306a36Sopenharmony_ci{ 274862306a36Sopenharmony_ci struct ethtool_fecparam fecparam; 274962306a36Sopenharmony_ci 275062306a36Sopenharmony_ci if (!dev->ethtool_ops->set_fecparam) 275162306a36Sopenharmony_ci return -EOPNOTSUPP; 275262306a36Sopenharmony_ci 275362306a36Sopenharmony_ci if (copy_from_user(&fecparam, useraddr, sizeof(fecparam))) 275462306a36Sopenharmony_ci return -EFAULT; 275562306a36Sopenharmony_ci 275662306a36Sopenharmony_ci if (!fecparam.fec || fecparam.fec & ETHTOOL_FEC_NONE) 275762306a36Sopenharmony_ci return -EINVAL; 275862306a36Sopenharmony_ci 275962306a36Sopenharmony_ci fecparam.active_fec = 0; 276062306a36Sopenharmony_ci fecparam.reserved = 0; 276162306a36Sopenharmony_ci 276262306a36Sopenharmony_ci return dev->ethtool_ops->set_fecparam(dev, &fecparam); 276362306a36Sopenharmony_ci} 276462306a36Sopenharmony_ci 276562306a36Sopenharmony_ci/* The main entry point in this file. Called from net/core/dev_ioctl.c */ 276662306a36Sopenharmony_ci 276762306a36Sopenharmony_cistatic int 276862306a36Sopenharmony_ci__dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr, 276962306a36Sopenharmony_ci u32 ethcmd, struct ethtool_devlink_compat *devlink_state) 277062306a36Sopenharmony_ci{ 277162306a36Sopenharmony_ci struct net_device *dev; 277262306a36Sopenharmony_ci u32 sub_cmd; 277362306a36Sopenharmony_ci int rc; 277462306a36Sopenharmony_ci netdev_features_t old_features; 277562306a36Sopenharmony_ci 277662306a36Sopenharmony_ci dev = __dev_get_by_name(net, ifr->ifr_name); 277762306a36Sopenharmony_ci if (!dev) 277862306a36Sopenharmony_ci return -ENODEV; 277962306a36Sopenharmony_ci 278062306a36Sopenharmony_ci if (ethcmd == ETHTOOL_PERQUEUE) { 278162306a36Sopenharmony_ci if (copy_from_user(&sub_cmd, useraddr + sizeof(ethcmd), sizeof(sub_cmd))) 278262306a36Sopenharmony_ci return -EFAULT; 278362306a36Sopenharmony_ci } else { 278462306a36Sopenharmony_ci sub_cmd = ethcmd; 278562306a36Sopenharmony_ci } 278662306a36Sopenharmony_ci /* Allow some commands to be done by anyone */ 278762306a36Sopenharmony_ci switch (sub_cmd) { 278862306a36Sopenharmony_ci case ETHTOOL_GSET: 278962306a36Sopenharmony_ci case ETHTOOL_GDRVINFO: 279062306a36Sopenharmony_ci case ETHTOOL_GMSGLVL: 279162306a36Sopenharmony_ci case ETHTOOL_GLINK: 279262306a36Sopenharmony_ci case ETHTOOL_GCOALESCE: 279362306a36Sopenharmony_ci case ETHTOOL_GRINGPARAM: 279462306a36Sopenharmony_ci case ETHTOOL_GPAUSEPARAM: 279562306a36Sopenharmony_ci case ETHTOOL_GRXCSUM: 279662306a36Sopenharmony_ci case ETHTOOL_GTXCSUM: 279762306a36Sopenharmony_ci case ETHTOOL_GSG: 279862306a36Sopenharmony_ci case ETHTOOL_GSSET_INFO: 279962306a36Sopenharmony_ci case ETHTOOL_GSTRINGS: 280062306a36Sopenharmony_ci case ETHTOOL_GSTATS: 280162306a36Sopenharmony_ci case ETHTOOL_GPHYSTATS: 280262306a36Sopenharmony_ci case ETHTOOL_GTSO: 280362306a36Sopenharmony_ci case ETHTOOL_GPERMADDR: 280462306a36Sopenharmony_ci case ETHTOOL_GUFO: 280562306a36Sopenharmony_ci case ETHTOOL_GGSO: 280662306a36Sopenharmony_ci case ETHTOOL_GGRO: 280762306a36Sopenharmony_ci case ETHTOOL_GFLAGS: 280862306a36Sopenharmony_ci case ETHTOOL_GPFLAGS: 280962306a36Sopenharmony_ci case ETHTOOL_GRXFH: 281062306a36Sopenharmony_ci case ETHTOOL_GRXRINGS: 281162306a36Sopenharmony_ci case ETHTOOL_GRXCLSRLCNT: 281262306a36Sopenharmony_ci case ETHTOOL_GRXCLSRULE: 281362306a36Sopenharmony_ci case ETHTOOL_GRXCLSRLALL: 281462306a36Sopenharmony_ci case ETHTOOL_GRXFHINDIR: 281562306a36Sopenharmony_ci case ETHTOOL_GRSSH: 281662306a36Sopenharmony_ci case ETHTOOL_GFEATURES: 281762306a36Sopenharmony_ci case ETHTOOL_GCHANNELS: 281862306a36Sopenharmony_ci case ETHTOOL_GET_TS_INFO: 281962306a36Sopenharmony_ci case ETHTOOL_GEEE: 282062306a36Sopenharmony_ci case ETHTOOL_GTUNABLE: 282162306a36Sopenharmony_ci case ETHTOOL_PHY_GTUNABLE: 282262306a36Sopenharmony_ci case ETHTOOL_GLINKSETTINGS: 282362306a36Sopenharmony_ci case ETHTOOL_GFECPARAM: 282462306a36Sopenharmony_ci break; 282562306a36Sopenharmony_ci default: 282662306a36Sopenharmony_ci if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) 282762306a36Sopenharmony_ci return -EPERM; 282862306a36Sopenharmony_ci } 282962306a36Sopenharmony_ci 283062306a36Sopenharmony_ci if (dev->dev.parent) 283162306a36Sopenharmony_ci pm_runtime_get_sync(dev->dev.parent); 283262306a36Sopenharmony_ci 283362306a36Sopenharmony_ci if (!netif_device_present(dev)) { 283462306a36Sopenharmony_ci rc = -ENODEV; 283562306a36Sopenharmony_ci goto out; 283662306a36Sopenharmony_ci } 283762306a36Sopenharmony_ci 283862306a36Sopenharmony_ci if (dev->ethtool_ops->begin) { 283962306a36Sopenharmony_ci rc = dev->ethtool_ops->begin(dev); 284062306a36Sopenharmony_ci if (rc < 0) 284162306a36Sopenharmony_ci goto out; 284262306a36Sopenharmony_ci } 284362306a36Sopenharmony_ci old_features = dev->features; 284462306a36Sopenharmony_ci 284562306a36Sopenharmony_ci switch (ethcmd) { 284662306a36Sopenharmony_ci case ETHTOOL_GSET: 284762306a36Sopenharmony_ci rc = ethtool_get_settings(dev, useraddr); 284862306a36Sopenharmony_ci break; 284962306a36Sopenharmony_ci case ETHTOOL_SSET: 285062306a36Sopenharmony_ci rc = ethtool_set_settings(dev, useraddr); 285162306a36Sopenharmony_ci break; 285262306a36Sopenharmony_ci case ETHTOOL_GDRVINFO: 285362306a36Sopenharmony_ci rc = ethtool_get_drvinfo(dev, devlink_state); 285462306a36Sopenharmony_ci break; 285562306a36Sopenharmony_ci case ETHTOOL_GREGS: 285662306a36Sopenharmony_ci rc = ethtool_get_regs(dev, useraddr); 285762306a36Sopenharmony_ci break; 285862306a36Sopenharmony_ci case ETHTOOL_GWOL: 285962306a36Sopenharmony_ci rc = ethtool_get_wol(dev, useraddr); 286062306a36Sopenharmony_ci break; 286162306a36Sopenharmony_ci case ETHTOOL_SWOL: 286262306a36Sopenharmony_ci rc = ethtool_set_wol(dev, useraddr); 286362306a36Sopenharmony_ci break; 286462306a36Sopenharmony_ci case ETHTOOL_GMSGLVL: 286562306a36Sopenharmony_ci rc = ethtool_get_value(dev, useraddr, ethcmd, 286662306a36Sopenharmony_ci dev->ethtool_ops->get_msglevel); 286762306a36Sopenharmony_ci break; 286862306a36Sopenharmony_ci case ETHTOOL_SMSGLVL: 286962306a36Sopenharmony_ci rc = ethtool_set_value_void(dev, useraddr, 287062306a36Sopenharmony_ci dev->ethtool_ops->set_msglevel); 287162306a36Sopenharmony_ci if (!rc) 287262306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_DEBUG_NTF, NULL); 287362306a36Sopenharmony_ci break; 287462306a36Sopenharmony_ci case ETHTOOL_GEEE: 287562306a36Sopenharmony_ci rc = ethtool_get_eee(dev, useraddr); 287662306a36Sopenharmony_ci break; 287762306a36Sopenharmony_ci case ETHTOOL_SEEE: 287862306a36Sopenharmony_ci rc = ethtool_set_eee(dev, useraddr); 287962306a36Sopenharmony_ci break; 288062306a36Sopenharmony_ci case ETHTOOL_NWAY_RST: 288162306a36Sopenharmony_ci rc = ethtool_nway_reset(dev); 288262306a36Sopenharmony_ci break; 288362306a36Sopenharmony_ci case ETHTOOL_GLINK: 288462306a36Sopenharmony_ci rc = ethtool_get_link(dev, useraddr); 288562306a36Sopenharmony_ci break; 288662306a36Sopenharmony_ci case ETHTOOL_GEEPROM: 288762306a36Sopenharmony_ci rc = ethtool_get_eeprom(dev, useraddr); 288862306a36Sopenharmony_ci break; 288962306a36Sopenharmony_ci case ETHTOOL_SEEPROM: 289062306a36Sopenharmony_ci rc = ethtool_set_eeprom(dev, useraddr); 289162306a36Sopenharmony_ci break; 289262306a36Sopenharmony_ci case ETHTOOL_GCOALESCE: 289362306a36Sopenharmony_ci rc = ethtool_get_coalesce(dev, useraddr); 289462306a36Sopenharmony_ci break; 289562306a36Sopenharmony_ci case ETHTOOL_SCOALESCE: 289662306a36Sopenharmony_ci rc = ethtool_set_coalesce(dev, useraddr); 289762306a36Sopenharmony_ci break; 289862306a36Sopenharmony_ci case ETHTOOL_GRINGPARAM: 289962306a36Sopenharmony_ci rc = ethtool_get_ringparam(dev, useraddr); 290062306a36Sopenharmony_ci break; 290162306a36Sopenharmony_ci case ETHTOOL_SRINGPARAM: 290262306a36Sopenharmony_ci rc = ethtool_set_ringparam(dev, useraddr); 290362306a36Sopenharmony_ci break; 290462306a36Sopenharmony_ci case ETHTOOL_GPAUSEPARAM: 290562306a36Sopenharmony_ci rc = ethtool_get_pauseparam(dev, useraddr); 290662306a36Sopenharmony_ci break; 290762306a36Sopenharmony_ci case ETHTOOL_SPAUSEPARAM: 290862306a36Sopenharmony_ci rc = ethtool_set_pauseparam(dev, useraddr); 290962306a36Sopenharmony_ci break; 291062306a36Sopenharmony_ci case ETHTOOL_TEST: 291162306a36Sopenharmony_ci rc = ethtool_self_test(dev, useraddr); 291262306a36Sopenharmony_ci break; 291362306a36Sopenharmony_ci case ETHTOOL_GSTRINGS: 291462306a36Sopenharmony_ci rc = ethtool_get_strings(dev, useraddr); 291562306a36Sopenharmony_ci break; 291662306a36Sopenharmony_ci case ETHTOOL_PHYS_ID: 291762306a36Sopenharmony_ci rc = ethtool_phys_id(dev, useraddr); 291862306a36Sopenharmony_ci break; 291962306a36Sopenharmony_ci case ETHTOOL_GSTATS: 292062306a36Sopenharmony_ci rc = ethtool_get_stats(dev, useraddr); 292162306a36Sopenharmony_ci break; 292262306a36Sopenharmony_ci case ETHTOOL_GPERMADDR: 292362306a36Sopenharmony_ci rc = ethtool_get_perm_addr(dev, useraddr); 292462306a36Sopenharmony_ci break; 292562306a36Sopenharmony_ci case ETHTOOL_GFLAGS: 292662306a36Sopenharmony_ci rc = ethtool_get_value(dev, useraddr, ethcmd, 292762306a36Sopenharmony_ci __ethtool_get_flags); 292862306a36Sopenharmony_ci break; 292962306a36Sopenharmony_ci case ETHTOOL_SFLAGS: 293062306a36Sopenharmony_ci rc = ethtool_set_value(dev, useraddr, __ethtool_set_flags); 293162306a36Sopenharmony_ci break; 293262306a36Sopenharmony_ci case ETHTOOL_GPFLAGS: 293362306a36Sopenharmony_ci rc = ethtool_get_value(dev, useraddr, ethcmd, 293462306a36Sopenharmony_ci dev->ethtool_ops->get_priv_flags); 293562306a36Sopenharmony_ci if (!rc) 293662306a36Sopenharmony_ci ethtool_notify(dev, ETHTOOL_MSG_PRIVFLAGS_NTF, NULL); 293762306a36Sopenharmony_ci break; 293862306a36Sopenharmony_ci case ETHTOOL_SPFLAGS: 293962306a36Sopenharmony_ci rc = ethtool_set_value(dev, useraddr, 294062306a36Sopenharmony_ci dev->ethtool_ops->set_priv_flags); 294162306a36Sopenharmony_ci break; 294262306a36Sopenharmony_ci case ETHTOOL_GRXFH: 294362306a36Sopenharmony_ci case ETHTOOL_GRXRINGS: 294462306a36Sopenharmony_ci case ETHTOOL_GRXCLSRLCNT: 294562306a36Sopenharmony_ci case ETHTOOL_GRXCLSRULE: 294662306a36Sopenharmony_ci case ETHTOOL_GRXCLSRLALL: 294762306a36Sopenharmony_ci rc = ethtool_get_rxnfc(dev, ethcmd, useraddr); 294862306a36Sopenharmony_ci break; 294962306a36Sopenharmony_ci case ETHTOOL_SRXFH: 295062306a36Sopenharmony_ci case ETHTOOL_SRXCLSRLDEL: 295162306a36Sopenharmony_ci case ETHTOOL_SRXCLSRLINS: 295262306a36Sopenharmony_ci rc = ethtool_set_rxnfc(dev, ethcmd, useraddr); 295362306a36Sopenharmony_ci break; 295462306a36Sopenharmony_ci case ETHTOOL_FLASHDEV: 295562306a36Sopenharmony_ci rc = ethtool_flash_device(dev, devlink_state); 295662306a36Sopenharmony_ci break; 295762306a36Sopenharmony_ci case ETHTOOL_RESET: 295862306a36Sopenharmony_ci rc = ethtool_reset(dev, useraddr); 295962306a36Sopenharmony_ci break; 296062306a36Sopenharmony_ci case ETHTOOL_GSSET_INFO: 296162306a36Sopenharmony_ci rc = ethtool_get_sset_info(dev, useraddr); 296262306a36Sopenharmony_ci break; 296362306a36Sopenharmony_ci case ETHTOOL_GRXFHINDIR: 296462306a36Sopenharmony_ci rc = ethtool_get_rxfh_indir(dev, useraddr); 296562306a36Sopenharmony_ci break; 296662306a36Sopenharmony_ci case ETHTOOL_SRXFHINDIR: 296762306a36Sopenharmony_ci rc = ethtool_set_rxfh_indir(dev, useraddr); 296862306a36Sopenharmony_ci break; 296962306a36Sopenharmony_ci case ETHTOOL_GRSSH: 297062306a36Sopenharmony_ci rc = ethtool_get_rxfh(dev, useraddr); 297162306a36Sopenharmony_ci break; 297262306a36Sopenharmony_ci case ETHTOOL_SRSSH: 297362306a36Sopenharmony_ci rc = ethtool_set_rxfh(dev, useraddr); 297462306a36Sopenharmony_ci break; 297562306a36Sopenharmony_ci case ETHTOOL_GFEATURES: 297662306a36Sopenharmony_ci rc = ethtool_get_features(dev, useraddr); 297762306a36Sopenharmony_ci break; 297862306a36Sopenharmony_ci case ETHTOOL_SFEATURES: 297962306a36Sopenharmony_ci rc = ethtool_set_features(dev, useraddr); 298062306a36Sopenharmony_ci break; 298162306a36Sopenharmony_ci case ETHTOOL_GTXCSUM: 298262306a36Sopenharmony_ci case ETHTOOL_GRXCSUM: 298362306a36Sopenharmony_ci case ETHTOOL_GSG: 298462306a36Sopenharmony_ci case ETHTOOL_GTSO: 298562306a36Sopenharmony_ci case ETHTOOL_GGSO: 298662306a36Sopenharmony_ci case ETHTOOL_GGRO: 298762306a36Sopenharmony_ci rc = ethtool_get_one_feature(dev, useraddr, ethcmd); 298862306a36Sopenharmony_ci break; 298962306a36Sopenharmony_ci case ETHTOOL_STXCSUM: 299062306a36Sopenharmony_ci case ETHTOOL_SRXCSUM: 299162306a36Sopenharmony_ci case ETHTOOL_SSG: 299262306a36Sopenharmony_ci case ETHTOOL_STSO: 299362306a36Sopenharmony_ci case ETHTOOL_SGSO: 299462306a36Sopenharmony_ci case ETHTOOL_SGRO: 299562306a36Sopenharmony_ci rc = ethtool_set_one_feature(dev, useraddr, ethcmd); 299662306a36Sopenharmony_ci break; 299762306a36Sopenharmony_ci case ETHTOOL_GCHANNELS: 299862306a36Sopenharmony_ci rc = ethtool_get_channels(dev, useraddr); 299962306a36Sopenharmony_ci break; 300062306a36Sopenharmony_ci case ETHTOOL_SCHANNELS: 300162306a36Sopenharmony_ci rc = ethtool_set_channels(dev, useraddr); 300262306a36Sopenharmony_ci break; 300362306a36Sopenharmony_ci case ETHTOOL_SET_DUMP: 300462306a36Sopenharmony_ci rc = ethtool_set_dump(dev, useraddr); 300562306a36Sopenharmony_ci break; 300662306a36Sopenharmony_ci case ETHTOOL_GET_DUMP_FLAG: 300762306a36Sopenharmony_ci rc = ethtool_get_dump_flag(dev, useraddr); 300862306a36Sopenharmony_ci break; 300962306a36Sopenharmony_ci case ETHTOOL_GET_DUMP_DATA: 301062306a36Sopenharmony_ci rc = ethtool_get_dump_data(dev, useraddr); 301162306a36Sopenharmony_ci break; 301262306a36Sopenharmony_ci case ETHTOOL_GET_TS_INFO: 301362306a36Sopenharmony_ci rc = ethtool_get_ts_info(dev, useraddr); 301462306a36Sopenharmony_ci break; 301562306a36Sopenharmony_ci case ETHTOOL_GMODULEINFO: 301662306a36Sopenharmony_ci rc = ethtool_get_module_info(dev, useraddr); 301762306a36Sopenharmony_ci break; 301862306a36Sopenharmony_ci case ETHTOOL_GMODULEEEPROM: 301962306a36Sopenharmony_ci rc = ethtool_get_module_eeprom(dev, useraddr); 302062306a36Sopenharmony_ci break; 302162306a36Sopenharmony_ci case ETHTOOL_GTUNABLE: 302262306a36Sopenharmony_ci rc = ethtool_get_tunable(dev, useraddr); 302362306a36Sopenharmony_ci break; 302462306a36Sopenharmony_ci case ETHTOOL_STUNABLE: 302562306a36Sopenharmony_ci rc = ethtool_set_tunable(dev, useraddr); 302662306a36Sopenharmony_ci break; 302762306a36Sopenharmony_ci case ETHTOOL_GPHYSTATS: 302862306a36Sopenharmony_ci rc = ethtool_get_phy_stats(dev, useraddr); 302962306a36Sopenharmony_ci break; 303062306a36Sopenharmony_ci case ETHTOOL_PERQUEUE: 303162306a36Sopenharmony_ci rc = ethtool_set_per_queue(dev, useraddr, sub_cmd); 303262306a36Sopenharmony_ci break; 303362306a36Sopenharmony_ci case ETHTOOL_GLINKSETTINGS: 303462306a36Sopenharmony_ci rc = ethtool_get_link_ksettings(dev, useraddr); 303562306a36Sopenharmony_ci break; 303662306a36Sopenharmony_ci case ETHTOOL_SLINKSETTINGS: 303762306a36Sopenharmony_ci rc = ethtool_set_link_ksettings(dev, useraddr); 303862306a36Sopenharmony_ci break; 303962306a36Sopenharmony_ci case ETHTOOL_PHY_GTUNABLE: 304062306a36Sopenharmony_ci rc = get_phy_tunable(dev, useraddr); 304162306a36Sopenharmony_ci break; 304262306a36Sopenharmony_ci case ETHTOOL_PHY_STUNABLE: 304362306a36Sopenharmony_ci rc = set_phy_tunable(dev, useraddr); 304462306a36Sopenharmony_ci break; 304562306a36Sopenharmony_ci case ETHTOOL_GFECPARAM: 304662306a36Sopenharmony_ci rc = ethtool_get_fecparam(dev, useraddr); 304762306a36Sopenharmony_ci break; 304862306a36Sopenharmony_ci case ETHTOOL_SFECPARAM: 304962306a36Sopenharmony_ci rc = ethtool_set_fecparam(dev, useraddr); 305062306a36Sopenharmony_ci break; 305162306a36Sopenharmony_ci default: 305262306a36Sopenharmony_ci rc = -EOPNOTSUPP; 305362306a36Sopenharmony_ci } 305462306a36Sopenharmony_ci 305562306a36Sopenharmony_ci if (dev->ethtool_ops->complete) 305662306a36Sopenharmony_ci dev->ethtool_ops->complete(dev); 305762306a36Sopenharmony_ci 305862306a36Sopenharmony_ci if (old_features != dev->features) 305962306a36Sopenharmony_ci netdev_features_change(dev); 306062306a36Sopenharmony_ciout: 306162306a36Sopenharmony_ci if (dev->dev.parent) 306262306a36Sopenharmony_ci pm_runtime_put(dev->dev.parent); 306362306a36Sopenharmony_ci 306462306a36Sopenharmony_ci return rc; 306562306a36Sopenharmony_ci} 306662306a36Sopenharmony_ci 306762306a36Sopenharmony_ciint dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr) 306862306a36Sopenharmony_ci{ 306962306a36Sopenharmony_ci struct ethtool_devlink_compat *state; 307062306a36Sopenharmony_ci u32 ethcmd; 307162306a36Sopenharmony_ci int rc; 307262306a36Sopenharmony_ci 307362306a36Sopenharmony_ci if (copy_from_user(ðcmd, useraddr, sizeof(ethcmd))) 307462306a36Sopenharmony_ci return -EFAULT; 307562306a36Sopenharmony_ci 307662306a36Sopenharmony_ci state = kzalloc(sizeof(*state), GFP_KERNEL); 307762306a36Sopenharmony_ci if (!state) 307862306a36Sopenharmony_ci return -ENOMEM; 307962306a36Sopenharmony_ci 308062306a36Sopenharmony_ci switch (ethcmd) { 308162306a36Sopenharmony_ci case ETHTOOL_FLASHDEV: 308262306a36Sopenharmony_ci if (copy_from_user(&state->efl, useraddr, sizeof(state->efl))) { 308362306a36Sopenharmony_ci rc = -EFAULT; 308462306a36Sopenharmony_ci goto exit_free; 308562306a36Sopenharmony_ci } 308662306a36Sopenharmony_ci state->efl.data[ETHTOOL_FLASH_MAX_FILENAME - 1] = 0; 308762306a36Sopenharmony_ci break; 308862306a36Sopenharmony_ci } 308962306a36Sopenharmony_ci 309062306a36Sopenharmony_ci rtnl_lock(); 309162306a36Sopenharmony_ci rc = __dev_ethtool(net, ifr, useraddr, ethcmd, state); 309262306a36Sopenharmony_ci rtnl_unlock(); 309362306a36Sopenharmony_ci if (rc) 309462306a36Sopenharmony_ci goto exit_free; 309562306a36Sopenharmony_ci 309662306a36Sopenharmony_ci switch (ethcmd) { 309762306a36Sopenharmony_ci case ETHTOOL_FLASHDEV: 309862306a36Sopenharmony_ci if (state->devlink) 309962306a36Sopenharmony_ci rc = devlink_compat_flash_update(state->devlink, 310062306a36Sopenharmony_ci state->efl.data); 310162306a36Sopenharmony_ci break; 310262306a36Sopenharmony_ci case ETHTOOL_GDRVINFO: 310362306a36Sopenharmony_ci if (state->devlink) 310462306a36Sopenharmony_ci devlink_compat_running_version(state->devlink, 310562306a36Sopenharmony_ci state->info.fw_version, 310662306a36Sopenharmony_ci sizeof(state->info.fw_version)); 310762306a36Sopenharmony_ci if (copy_to_user(useraddr, &state->info, sizeof(state->info))) { 310862306a36Sopenharmony_ci rc = -EFAULT; 310962306a36Sopenharmony_ci goto exit_free; 311062306a36Sopenharmony_ci } 311162306a36Sopenharmony_ci break; 311262306a36Sopenharmony_ci } 311362306a36Sopenharmony_ci 311462306a36Sopenharmony_ciexit_free: 311562306a36Sopenharmony_ci if (state->devlink) 311662306a36Sopenharmony_ci devlink_put(state->devlink); 311762306a36Sopenharmony_ci kfree(state); 311862306a36Sopenharmony_ci return rc; 311962306a36Sopenharmony_ci} 312062306a36Sopenharmony_ci 312162306a36Sopenharmony_cistruct ethtool_rx_flow_key { 312262306a36Sopenharmony_ci struct flow_dissector_key_basic basic; 312362306a36Sopenharmony_ci union { 312462306a36Sopenharmony_ci struct flow_dissector_key_ipv4_addrs ipv4; 312562306a36Sopenharmony_ci struct flow_dissector_key_ipv6_addrs ipv6; 312662306a36Sopenharmony_ci }; 312762306a36Sopenharmony_ci struct flow_dissector_key_ports tp; 312862306a36Sopenharmony_ci struct flow_dissector_key_ip ip; 312962306a36Sopenharmony_ci struct flow_dissector_key_vlan vlan; 313062306a36Sopenharmony_ci struct flow_dissector_key_eth_addrs eth_addrs; 313162306a36Sopenharmony_ci} __aligned(BITS_PER_LONG / 8); /* Ensure that we can do comparisons as longs. */ 313262306a36Sopenharmony_ci 313362306a36Sopenharmony_cistruct ethtool_rx_flow_match { 313462306a36Sopenharmony_ci struct flow_dissector dissector; 313562306a36Sopenharmony_ci struct ethtool_rx_flow_key key; 313662306a36Sopenharmony_ci struct ethtool_rx_flow_key mask; 313762306a36Sopenharmony_ci}; 313862306a36Sopenharmony_ci 313962306a36Sopenharmony_cistruct ethtool_rx_flow_rule * 314062306a36Sopenharmony_ciethtool_rx_flow_rule_create(const struct ethtool_rx_flow_spec_input *input) 314162306a36Sopenharmony_ci{ 314262306a36Sopenharmony_ci const struct ethtool_rx_flow_spec *fs = input->fs; 314362306a36Sopenharmony_ci struct ethtool_rx_flow_match *match; 314462306a36Sopenharmony_ci struct ethtool_rx_flow_rule *flow; 314562306a36Sopenharmony_ci struct flow_action_entry *act; 314662306a36Sopenharmony_ci 314762306a36Sopenharmony_ci flow = kzalloc(sizeof(struct ethtool_rx_flow_rule) + 314862306a36Sopenharmony_ci sizeof(struct ethtool_rx_flow_match), GFP_KERNEL); 314962306a36Sopenharmony_ci if (!flow) 315062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 315162306a36Sopenharmony_ci 315262306a36Sopenharmony_ci /* ethtool_rx supports only one single action per rule. */ 315362306a36Sopenharmony_ci flow->rule = flow_rule_alloc(1); 315462306a36Sopenharmony_ci if (!flow->rule) { 315562306a36Sopenharmony_ci kfree(flow); 315662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 315762306a36Sopenharmony_ci } 315862306a36Sopenharmony_ci 315962306a36Sopenharmony_ci match = (struct ethtool_rx_flow_match *)flow->priv; 316062306a36Sopenharmony_ci flow->rule->match.dissector = &match->dissector; 316162306a36Sopenharmony_ci flow->rule->match.mask = &match->mask; 316262306a36Sopenharmony_ci flow->rule->match.key = &match->key; 316362306a36Sopenharmony_ci 316462306a36Sopenharmony_ci match->mask.basic.n_proto = htons(0xffff); 316562306a36Sopenharmony_ci 316662306a36Sopenharmony_ci switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS)) { 316762306a36Sopenharmony_ci case ETHER_FLOW: { 316862306a36Sopenharmony_ci const struct ethhdr *ether_spec, *ether_m_spec; 316962306a36Sopenharmony_ci 317062306a36Sopenharmony_ci ether_spec = &fs->h_u.ether_spec; 317162306a36Sopenharmony_ci ether_m_spec = &fs->m_u.ether_spec; 317262306a36Sopenharmony_ci 317362306a36Sopenharmony_ci if (!is_zero_ether_addr(ether_m_spec->h_source)) { 317462306a36Sopenharmony_ci ether_addr_copy(match->key.eth_addrs.src, 317562306a36Sopenharmony_ci ether_spec->h_source); 317662306a36Sopenharmony_ci ether_addr_copy(match->mask.eth_addrs.src, 317762306a36Sopenharmony_ci ether_m_spec->h_source); 317862306a36Sopenharmony_ci } 317962306a36Sopenharmony_ci if (!is_zero_ether_addr(ether_m_spec->h_dest)) { 318062306a36Sopenharmony_ci ether_addr_copy(match->key.eth_addrs.dst, 318162306a36Sopenharmony_ci ether_spec->h_dest); 318262306a36Sopenharmony_ci ether_addr_copy(match->mask.eth_addrs.dst, 318362306a36Sopenharmony_ci ether_m_spec->h_dest); 318462306a36Sopenharmony_ci } 318562306a36Sopenharmony_ci if (ether_m_spec->h_proto) { 318662306a36Sopenharmony_ci match->key.basic.n_proto = ether_spec->h_proto; 318762306a36Sopenharmony_ci match->mask.basic.n_proto = ether_m_spec->h_proto; 318862306a36Sopenharmony_ci } 318962306a36Sopenharmony_ci } 319062306a36Sopenharmony_ci break; 319162306a36Sopenharmony_ci case TCP_V4_FLOW: 319262306a36Sopenharmony_ci case UDP_V4_FLOW: { 319362306a36Sopenharmony_ci const struct ethtool_tcpip4_spec *v4_spec, *v4_m_spec; 319462306a36Sopenharmony_ci 319562306a36Sopenharmony_ci match->key.basic.n_proto = htons(ETH_P_IP); 319662306a36Sopenharmony_ci 319762306a36Sopenharmony_ci v4_spec = &fs->h_u.tcp_ip4_spec; 319862306a36Sopenharmony_ci v4_m_spec = &fs->m_u.tcp_ip4_spec; 319962306a36Sopenharmony_ci 320062306a36Sopenharmony_ci if (v4_m_spec->ip4src) { 320162306a36Sopenharmony_ci match->key.ipv4.src = v4_spec->ip4src; 320262306a36Sopenharmony_ci match->mask.ipv4.src = v4_m_spec->ip4src; 320362306a36Sopenharmony_ci } 320462306a36Sopenharmony_ci if (v4_m_spec->ip4dst) { 320562306a36Sopenharmony_ci match->key.ipv4.dst = v4_spec->ip4dst; 320662306a36Sopenharmony_ci match->mask.ipv4.dst = v4_m_spec->ip4dst; 320762306a36Sopenharmony_ci } 320862306a36Sopenharmony_ci if (v4_m_spec->ip4src || 320962306a36Sopenharmony_ci v4_m_spec->ip4dst) { 321062306a36Sopenharmony_ci match->dissector.used_keys |= 321162306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS); 321262306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_IPV4_ADDRS] = 321362306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, ipv4); 321462306a36Sopenharmony_ci } 321562306a36Sopenharmony_ci if (v4_m_spec->psrc) { 321662306a36Sopenharmony_ci match->key.tp.src = v4_spec->psrc; 321762306a36Sopenharmony_ci match->mask.tp.src = v4_m_spec->psrc; 321862306a36Sopenharmony_ci } 321962306a36Sopenharmony_ci if (v4_m_spec->pdst) { 322062306a36Sopenharmony_ci match->key.tp.dst = v4_spec->pdst; 322162306a36Sopenharmony_ci match->mask.tp.dst = v4_m_spec->pdst; 322262306a36Sopenharmony_ci } 322362306a36Sopenharmony_ci if (v4_m_spec->psrc || 322462306a36Sopenharmony_ci v4_m_spec->pdst) { 322562306a36Sopenharmony_ci match->dissector.used_keys |= 322662306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_PORTS); 322762306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_PORTS] = 322862306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, tp); 322962306a36Sopenharmony_ci } 323062306a36Sopenharmony_ci if (v4_m_spec->tos) { 323162306a36Sopenharmony_ci match->key.ip.tos = v4_spec->tos; 323262306a36Sopenharmony_ci match->mask.ip.tos = v4_m_spec->tos; 323362306a36Sopenharmony_ci match->dissector.used_keys |= 323462306a36Sopenharmony_ci BIT(FLOW_DISSECTOR_KEY_IP); 323562306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_IP] = 323662306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, ip); 323762306a36Sopenharmony_ci } 323862306a36Sopenharmony_ci } 323962306a36Sopenharmony_ci break; 324062306a36Sopenharmony_ci case TCP_V6_FLOW: 324162306a36Sopenharmony_ci case UDP_V6_FLOW: { 324262306a36Sopenharmony_ci const struct ethtool_tcpip6_spec *v6_spec, *v6_m_spec; 324362306a36Sopenharmony_ci 324462306a36Sopenharmony_ci match->key.basic.n_proto = htons(ETH_P_IPV6); 324562306a36Sopenharmony_ci 324662306a36Sopenharmony_ci v6_spec = &fs->h_u.tcp_ip6_spec; 324762306a36Sopenharmony_ci v6_m_spec = &fs->m_u.tcp_ip6_spec; 324862306a36Sopenharmony_ci if (!ipv6_addr_any((struct in6_addr *)v6_m_spec->ip6src)) { 324962306a36Sopenharmony_ci memcpy(&match->key.ipv6.src, v6_spec->ip6src, 325062306a36Sopenharmony_ci sizeof(match->key.ipv6.src)); 325162306a36Sopenharmony_ci memcpy(&match->mask.ipv6.src, v6_m_spec->ip6src, 325262306a36Sopenharmony_ci sizeof(match->mask.ipv6.src)); 325362306a36Sopenharmony_ci } 325462306a36Sopenharmony_ci if (!ipv6_addr_any((struct in6_addr *)v6_m_spec->ip6dst)) { 325562306a36Sopenharmony_ci memcpy(&match->key.ipv6.dst, v6_spec->ip6dst, 325662306a36Sopenharmony_ci sizeof(match->key.ipv6.dst)); 325762306a36Sopenharmony_ci memcpy(&match->mask.ipv6.dst, v6_m_spec->ip6dst, 325862306a36Sopenharmony_ci sizeof(match->mask.ipv6.dst)); 325962306a36Sopenharmony_ci } 326062306a36Sopenharmony_ci if (!ipv6_addr_any((struct in6_addr *)v6_m_spec->ip6src) || 326162306a36Sopenharmony_ci !ipv6_addr_any((struct in6_addr *)v6_m_spec->ip6dst)) { 326262306a36Sopenharmony_ci match->dissector.used_keys |= 326362306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS); 326462306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_IPV6_ADDRS] = 326562306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, ipv6); 326662306a36Sopenharmony_ci } 326762306a36Sopenharmony_ci if (v6_m_spec->psrc) { 326862306a36Sopenharmony_ci match->key.tp.src = v6_spec->psrc; 326962306a36Sopenharmony_ci match->mask.tp.src = v6_m_spec->psrc; 327062306a36Sopenharmony_ci } 327162306a36Sopenharmony_ci if (v6_m_spec->pdst) { 327262306a36Sopenharmony_ci match->key.tp.dst = v6_spec->pdst; 327362306a36Sopenharmony_ci match->mask.tp.dst = v6_m_spec->pdst; 327462306a36Sopenharmony_ci } 327562306a36Sopenharmony_ci if (v6_m_spec->psrc || 327662306a36Sopenharmony_ci v6_m_spec->pdst) { 327762306a36Sopenharmony_ci match->dissector.used_keys |= 327862306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_PORTS); 327962306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_PORTS] = 328062306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, tp); 328162306a36Sopenharmony_ci } 328262306a36Sopenharmony_ci if (v6_m_spec->tclass) { 328362306a36Sopenharmony_ci match->key.ip.tos = v6_spec->tclass; 328462306a36Sopenharmony_ci match->mask.ip.tos = v6_m_spec->tclass; 328562306a36Sopenharmony_ci match->dissector.used_keys |= 328662306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_IP); 328762306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_IP] = 328862306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, ip); 328962306a36Sopenharmony_ci } 329062306a36Sopenharmony_ci } 329162306a36Sopenharmony_ci break; 329262306a36Sopenharmony_ci default: 329362306a36Sopenharmony_ci ethtool_rx_flow_rule_destroy(flow); 329462306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 329562306a36Sopenharmony_ci } 329662306a36Sopenharmony_ci 329762306a36Sopenharmony_ci switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS)) { 329862306a36Sopenharmony_ci case TCP_V4_FLOW: 329962306a36Sopenharmony_ci case TCP_V6_FLOW: 330062306a36Sopenharmony_ci match->key.basic.ip_proto = IPPROTO_TCP; 330162306a36Sopenharmony_ci match->mask.basic.ip_proto = 0xff; 330262306a36Sopenharmony_ci break; 330362306a36Sopenharmony_ci case UDP_V4_FLOW: 330462306a36Sopenharmony_ci case UDP_V6_FLOW: 330562306a36Sopenharmony_ci match->key.basic.ip_proto = IPPROTO_UDP; 330662306a36Sopenharmony_ci match->mask.basic.ip_proto = 0xff; 330762306a36Sopenharmony_ci break; 330862306a36Sopenharmony_ci } 330962306a36Sopenharmony_ci 331062306a36Sopenharmony_ci match->dissector.used_keys |= BIT_ULL(FLOW_DISSECTOR_KEY_BASIC); 331162306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_BASIC] = 331262306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, basic); 331362306a36Sopenharmony_ci 331462306a36Sopenharmony_ci if (fs->flow_type & FLOW_EXT) { 331562306a36Sopenharmony_ci const struct ethtool_flow_ext *ext_h_spec = &fs->h_ext; 331662306a36Sopenharmony_ci const struct ethtool_flow_ext *ext_m_spec = &fs->m_ext; 331762306a36Sopenharmony_ci 331862306a36Sopenharmony_ci if (ext_m_spec->vlan_etype) { 331962306a36Sopenharmony_ci match->key.vlan.vlan_tpid = ext_h_spec->vlan_etype; 332062306a36Sopenharmony_ci match->mask.vlan.vlan_tpid = ext_m_spec->vlan_etype; 332162306a36Sopenharmony_ci } 332262306a36Sopenharmony_ci 332362306a36Sopenharmony_ci if (ext_m_spec->vlan_tci) { 332462306a36Sopenharmony_ci match->key.vlan.vlan_id = 332562306a36Sopenharmony_ci ntohs(ext_h_spec->vlan_tci) & 0x0fff; 332662306a36Sopenharmony_ci match->mask.vlan.vlan_id = 332762306a36Sopenharmony_ci ntohs(ext_m_spec->vlan_tci) & 0x0fff; 332862306a36Sopenharmony_ci 332962306a36Sopenharmony_ci match->key.vlan.vlan_dei = 333062306a36Sopenharmony_ci !!(ext_h_spec->vlan_tci & htons(0x1000)); 333162306a36Sopenharmony_ci match->mask.vlan.vlan_dei = 333262306a36Sopenharmony_ci !!(ext_m_spec->vlan_tci & htons(0x1000)); 333362306a36Sopenharmony_ci 333462306a36Sopenharmony_ci match->key.vlan.vlan_priority = 333562306a36Sopenharmony_ci (ntohs(ext_h_spec->vlan_tci) & 0xe000) >> 13; 333662306a36Sopenharmony_ci match->mask.vlan.vlan_priority = 333762306a36Sopenharmony_ci (ntohs(ext_m_spec->vlan_tci) & 0xe000) >> 13; 333862306a36Sopenharmony_ci } 333962306a36Sopenharmony_ci 334062306a36Sopenharmony_ci if (ext_m_spec->vlan_etype || 334162306a36Sopenharmony_ci ext_m_spec->vlan_tci) { 334262306a36Sopenharmony_ci match->dissector.used_keys |= 334362306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_VLAN); 334462306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_VLAN] = 334562306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, vlan); 334662306a36Sopenharmony_ci } 334762306a36Sopenharmony_ci } 334862306a36Sopenharmony_ci if (fs->flow_type & FLOW_MAC_EXT) { 334962306a36Sopenharmony_ci const struct ethtool_flow_ext *ext_h_spec = &fs->h_ext; 335062306a36Sopenharmony_ci const struct ethtool_flow_ext *ext_m_spec = &fs->m_ext; 335162306a36Sopenharmony_ci 335262306a36Sopenharmony_ci memcpy(match->key.eth_addrs.dst, ext_h_spec->h_dest, 335362306a36Sopenharmony_ci ETH_ALEN); 335462306a36Sopenharmony_ci memcpy(match->mask.eth_addrs.dst, ext_m_spec->h_dest, 335562306a36Sopenharmony_ci ETH_ALEN); 335662306a36Sopenharmony_ci 335762306a36Sopenharmony_ci match->dissector.used_keys |= 335862306a36Sopenharmony_ci BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS); 335962306a36Sopenharmony_ci match->dissector.offset[FLOW_DISSECTOR_KEY_ETH_ADDRS] = 336062306a36Sopenharmony_ci offsetof(struct ethtool_rx_flow_key, eth_addrs); 336162306a36Sopenharmony_ci } 336262306a36Sopenharmony_ci 336362306a36Sopenharmony_ci act = &flow->rule->action.entries[0]; 336462306a36Sopenharmony_ci switch (fs->ring_cookie) { 336562306a36Sopenharmony_ci case RX_CLS_FLOW_DISC: 336662306a36Sopenharmony_ci act->id = FLOW_ACTION_DROP; 336762306a36Sopenharmony_ci break; 336862306a36Sopenharmony_ci case RX_CLS_FLOW_WAKE: 336962306a36Sopenharmony_ci act->id = FLOW_ACTION_WAKE; 337062306a36Sopenharmony_ci break; 337162306a36Sopenharmony_ci default: 337262306a36Sopenharmony_ci act->id = FLOW_ACTION_QUEUE; 337362306a36Sopenharmony_ci if (fs->flow_type & FLOW_RSS) 337462306a36Sopenharmony_ci act->queue.ctx = input->rss_ctx; 337562306a36Sopenharmony_ci 337662306a36Sopenharmony_ci act->queue.vf = ethtool_get_flow_spec_ring_vf(fs->ring_cookie); 337762306a36Sopenharmony_ci act->queue.index = ethtool_get_flow_spec_ring(fs->ring_cookie); 337862306a36Sopenharmony_ci break; 337962306a36Sopenharmony_ci } 338062306a36Sopenharmony_ci 338162306a36Sopenharmony_ci return flow; 338262306a36Sopenharmony_ci} 338362306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_rx_flow_rule_create); 338462306a36Sopenharmony_ci 338562306a36Sopenharmony_civoid ethtool_rx_flow_rule_destroy(struct ethtool_rx_flow_rule *flow) 338662306a36Sopenharmony_ci{ 338762306a36Sopenharmony_ci kfree(flow->rule); 338862306a36Sopenharmony_ci kfree(flow); 338962306a36Sopenharmony_ci} 339062306a36Sopenharmony_ciEXPORT_SYMBOL(ethtool_rx_flow_rule_destroy); 3391