Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ioctl.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * net/core/ethtool.c - Ethtool ioctl handler
 * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx>
 *
 * This file is where we call all the ethtool_ops commands to get
 * the information ethtool needs.
 */


#include <linux/compat.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/bitops.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/sfp.h>
#include <linux/slab.h>
#include <linux/rtnetlink.h>
#include <linux/sched/signal.h>
#include <linux/net.h>
#include <linux/pm_runtime.h>
#include <linux/utsname.h>
#include <net/devlink.h>
#include <net/ipv6.h>
#include <net/xdp_sock_drv.h>
#include <net/flow_offload.h>
#include <net/netdev_lock.h>
#include <linux/ethtool_netlink.h>
#include "common.h"

/* State held across locks and calls for commands which have devlink fallback */
struct ethtool_devlink_compat {
 struct devlink *devlink;
 union {
  struct ethtool_flash efl;
  struct ethtool_drvinfo info;
 };
};

static struct devlink *netdev_to_devlink_get(struct net_device *dev)
{
 if (!dev->devlink_port)
  return NULL;
 return devlink_try_get(dev->devlink_port->devlink);
}

/*
 * Some useful ethtool_ops methods that're device independent.
 * If we find that all drivers want to do the same thing here,
 * we can turn these into dev_() function calls.
 */


u32 ethtool_op_get_link(struct net_device *dev)
{
 /* Synchronize carrier state with link watch, see also rtnl_getlink() */
 __linkwatch_sync_dev(dev);

 return netif_carrier_ok(dev) ? 1 : 0;
}
EXPORT_SYMBOL(ethtool_op_get_link);

int ethtool_op_get_ts_info(struct net_device *dev,
      struct kernel_ethtool_ts_info *info)
{
 info->so_timestamping =
  SOF_TIMESTAMPING_TX_SOFTWARE |
  SOF_TIMESTAMPING_RX_SOFTWARE |
  SOF_TIMESTAMPING_SOFTWARE;
 info->phc_index = -1;
 return 0;
}
EXPORT_SYMBOL(ethtool_op_get_ts_info);

/* Handlers for each ethtool command */

static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_gfeatures cmd = {
  .cmd = ETHTOOL_GFEATURES,
  .size = ETHTOOL_DEV_FEATURE_WORDS,
 };
 struct ethtool_get_features_block features[ETHTOOL_DEV_FEATURE_WORDS];
 u32 __user *sizeaddr;
 u32 copy_size;
 int i;

 /* in case feature bits run out again */
 BUILD_BUG_ON(ETHTOOL_DEV_FEATURE_WORDS * sizeof(u32) > sizeof(netdev_features_t));

 for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) {
  features[i].available = (u32)(dev->hw_features >> (32 * i));
  features[i].requested = (u32)(dev->wanted_features >> (32 * i));
  features[i].active = (u32)(dev->features >> (32 * i));
  features[i].never_changed =
   (u32)(NETIF_F_NEVER_CHANGE >> (32 * i));
 }

 sizeaddr = useraddr + offsetof(struct ethtool_gfeatures, size);
 if (get_user(copy_size, sizeaddr))
  return -EFAULT;

 if (copy_size > ETHTOOL_DEV_FEATURE_WORDS)
  copy_size = ETHTOOL_DEV_FEATURE_WORDS;

 if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
  return -EFAULT;
 useraddr += sizeof(cmd);
 if (copy_to_user(useraddr, features,
    array_size(copy_size, sizeof(*features))))
  return -EFAULT;

 return 0;
}

static int ethtool_set_features(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_sfeatures cmd;
 struct ethtool_set_features_block features[ETHTOOL_DEV_FEATURE_WORDS];
 netdev_features_t wanted = 0, valid = 0;
 int i, ret = 0;

 if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
  return -EFAULT;
 useraddr += sizeof(cmd);

 if (cmd.size != ETHTOOL_DEV_FEATURE_WORDS)
  return -EINVAL;

 if (copy_from_user(features, useraddr, sizeof(features)))
  return -EFAULT;

 for (i = 0; i < ETHTOOL_DEV_FEATURE_WORDS; ++i) {
  valid |= (netdev_features_t)features[i].valid << (32 * i);
  wanted |= (netdev_features_t)features[i].requested << (32 * i);
 }

 if (valid & ~NETIF_F_ETHTOOL_BITS)
  return -EINVAL;

 if (valid & ~dev->hw_features) {
  valid &= dev->hw_features;
  ret |= ETHTOOL_F_UNSUPPORTED;
 }

 dev->wanted_features &= ~valid;
 dev->wanted_features |= wanted & valid;
 __netdev_update_features(dev);

 if ((dev->wanted_features ^ dev->features) & valid)
  ret |= ETHTOOL_F_WISH;

 return ret;
}

static int __ethtool_get_sset_count(struct net_device *dev, int sset)
{
 const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
 const struct ethtool_ops *ops = dev->ethtool_ops;

 if (sset == ETH_SS_FEATURES)
  return ARRAY_SIZE(netdev_features_strings);

 if (sset == ETH_SS_RSS_HASH_FUNCS)
  return ARRAY_SIZE(rss_hash_func_strings);

 if (sset == ETH_SS_TUNABLES)
  return ARRAY_SIZE(tunable_strings);

 if (sset == ETH_SS_PHY_TUNABLES)
  return ARRAY_SIZE(phy_tunable_strings);

 if (sset == ETH_SS_PHY_STATS && dev->phydev &&
     !ops->get_ethtool_phy_stats &&
     phy_ops && phy_ops->get_sset_count)
  return phy_ops->get_sset_count(dev->phydev);

 if (sset == ETH_SS_LINK_MODES)
  return __ETHTOOL_LINK_MODE_MASK_NBITS;

 if (ops->get_sset_count && ops->get_strings)
  return ops->get_sset_count(dev, sset);
 else
  return -EOPNOTSUPP;
}

static void __ethtool_get_strings(struct net_device *dev,
 u32 stringset, u8 *data)
{
 const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
 const struct ethtool_ops *ops = dev->ethtool_ops;

 if (stringset == ETH_SS_FEATURES)
  memcpy(data, netdev_features_strings,
   sizeof(netdev_features_strings));
 else if (stringset == ETH_SS_RSS_HASH_FUNCS)
  memcpy(data, rss_hash_func_strings,
         sizeof(rss_hash_func_strings));
 else if (stringset == ETH_SS_TUNABLES)
  memcpy(data, tunable_strings, sizeof(tunable_strings));
 else if (stringset == ETH_SS_PHY_TUNABLES)
  memcpy(data, phy_tunable_strings, sizeof(phy_tunable_strings));
 else if (stringset == ETH_SS_PHY_STATS && dev->phydev &&
   !ops->get_ethtool_phy_stats && phy_ops &&
   phy_ops->get_strings)
  phy_ops->get_strings(dev->phydev, data);
 else if (stringset == ETH_SS_LINK_MODES)
  memcpy(data, link_mode_names,
         __ETHTOOL_LINK_MODE_MASK_NBITS * ETH_GSTRING_LEN);
 else
  /* ops->get_strings is valid because checked earlier */
  ops->get_strings(dev, stringset, data);
}

static netdev_features_t ethtool_get_feature_mask(u32 eth_cmd)
{
 /* feature masks of legacy discrete ethtool ops */

 switch (eth_cmd) {
 case ETHTOOL_GTXCSUM:
 case ETHTOOL_STXCSUM:
  return NETIF_F_CSUM_MASK | NETIF_F_FCOE_CRC |
         NETIF_F_SCTP_CRC;
 case ETHTOOL_GRXCSUM:
 case ETHTOOL_SRXCSUM:
  return NETIF_F_RXCSUM;
 case ETHTOOL_GSG:
 case ETHTOOL_SSG:
  return NETIF_F_SG | NETIF_F_FRAGLIST;
 case ETHTOOL_GTSO:
 case ETHTOOL_STSO:
  return NETIF_F_ALL_TSO;
 case ETHTOOL_GGSO:
 case ETHTOOL_SGSO:
  return NETIF_F_GSO;
 case ETHTOOL_GGRO:
 case ETHTOOL_SGRO:
  return NETIF_F_GRO;
 default:
  BUG();
 }
}

static int ethtool_get_one_feature(struct net_device *dev,
 char __user *useraddr, u32 ethcmd)
{
 netdev_features_t mask = ethtool_get_feature_mask(ethcmd);
 struct ethtool_value edata = {
  .cmd = ethcmd,
  .data = !!(dev->features & mask),
 };

 if (copy_to_user(useraddr, &edata, sizeof(edata)))
  return -EFAULT;
 return 0;
}

static int ethtool_set_one_feature(struct net_device *dev,
 void __user *useraddr, u32 ethcmd)
{
 struct ethtool_value edata;
 netdev_features_t mask;

 if (copy_from_user(&edata, useraddr, sizeof(edata)))
  return -EFAULT;

 mask = ethtool_get_feature_mask(ethcmd);
 mask &= dev->hw_features;
 if (!mask)
  return -EOPNOTSUPP;

 if (edata.data)
  dev->wanted_features |= mask;
 else
  dev->wanted_features &= ~mask;

 __netdev_update_features(dev);

 return 0;
}

#define ETH_ALL_FLAGS    (ETH_FLAG_LRO | ETH_FLAG_RXVLAN | ETH_FLAG_TXVLAN | \
     ETH_FLAG_NTUPLE | ETH_FLAG_RXHASH)
#define ETH_ALL_FEATURES (NETIF_F_LRO | NETIF_F_HW_VLAN_CTAG_RX | \
     NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_NTUPLE | \
     NETIF_F_RXHASH)

static u32 __ethtool_get_flags(struct net_device *dev)
{
 u32 flags = 0;

 if (dev->features & NETIF_F_LRO)
  flags |= ETH_FLAG_LRO;
 if (dev->features & NETIF_F_HW_VLAN_CTAG_RX)
  flags |= ETH_FLAG_RXVLAN;
 if (dev->features & NETIF_F_HW_VLAN_CTAG_TX)
  flags |= ETH_FLAG_TXVLAN;
 if (dev->features & NETIF_F_NTUPLE)
  flags |= ETH_FLAG_NTUPLE;
 if (dev->features & NETIF_F_RXHASH)
  flags |= ETH_FLAG_RXHASH;

 return flags;
}

static int __ethtool_set_flags(struct net_device *dev, u32 data)
{
 netdev_features_t features = 0, changed;

 if (data & ~ETH_ALL_FLAGS)
  return -EINVAL;

 if (data & ETH_FLAG_LRO)
  features |= NETIF_F_LRO;
 if (data & ETH_FLAG_RXVLAN)
  features |= NETIF_F_HW_VLAN_CTAG_RX;
 if (data & ETH_FLAG_TXVLAN)
  features |= NETIF_F_HW_VLAN_CTAG_TX;
 if (data & ETH_FLAG_NTUPLE)
  features |= NETIF_F_NTUPLE;
 if (data & ETH_FLAG_RXHASH)
  features |= NETIF_F_RXHASH;

 /* allow changing only bits set in hw_features */
 changed = (features ^ dev->features) & ETH_ALL_FEATURES;
 if (changed & ~dev->hw_features)
  return (changed & dev->hw_features) ? -EINVAL : -EOPNOTSUPP;

 dev->wanted_features =
  (dev->wanted_features & ~changed) | (features & changed);

 __netdev_update_features(dev);

 return 0;
}

/* Given two link masks, AND them together and save the result in dst. */
void ethtool_intersect_link_masks(struct ethtool_link_ksettings *dst,
      struct ethtool_link_ksettings *src)
{
 unsigned int size = BITS_TO_LONGS(__ETHTOOL_LINK_MODE_MASK_NBITS);
 unsigned int idx = 0;

 for (; idx < size; idx++) {
  dst->link_modes.supported[idx] &=
   src->link_modes.supported[idx];
  dst->link_modes.advertising[idx] &=
   src->link_modes.advertising[idx];
 }
}
EXPORT_SYMBOL(ethtool_intersect_link_masks);

void ethtool_convert_legacy_u32_to_link_mode(unsigned long *dst,
          u32 legacy_u32)
{
 linkmode_zero(dst);
 dst[0] = legacy_u32;
}
EXPORT_SYMBOL(ethtool_convert_legacy_u32_to_link_mode);

/* return false if src had higher bits set. lower bits always updated. */
bool ethtool_convert_link_mode_to_legacy_u32(u32 *legacy_u32,
          const unsigned long *src)
{
 *legacy_u32 = src[0];
 return find_next_bit(src, __ETHTOOL_LINK_MODE_MASK_NBITS, 32) ==
  __ETHTOOL_LINK_MODE_MASK_NBITS;
}
EXPORT_SYMBOL(ethtool_convert_link_mode_to_legacy_u32);

/* return false if ksettings link modes had higher bits
 * set. legacy_settings always updated (best effort)
 */

static bool
convert_link_ksettings_to_legacy_settings(
 struct ethtool_cmd *legacy_settings,
 const struct ethtool_link_ksettings *link_ksettings)
{
 bool retval = true;

 memset(legacy_settings, 0, sizeof(*legacy_settings));
 /* this also clears the deprecated fields in legacy structure:
 * __u8 transceiver;
 * __u32 maxtxpkt;
 * __u32 maxrxpkt;
 */


 retval &= ethtool_convert_link_mode_to_legacy_u32(
  &legacy_settings->supported,
  link_ksettings->link_modes.supported);
 retval &= ethtool_convert_link_mode_to_legacy_u32(
  &legacy_settings->advertising,
  link_ksettings->link_modes.advertising);
 retval &= ethtool_convert_link_mode_to_legacy_u32(
  &legacy_settings->lp_advertising,
  link_ksettings->link_modes.lp_advertising);
 ethtool_cmd_speed_set(legacy_settings, link_ksettings->base.speed);
 legacy_settings->duplex
  = link_ksettings->base.duplex;
 legacy_settings->port
  = link_ksettings->base.port;
 legacy_settings->phy_address
  = link_ksettings->base.phy_address;
 legacy_settings->autoneg
  = link_ksettings->base.autoneg;
 legacy_settings->mdio_support
  = link_ksettings->base.mdio_support;
 legacy_settings->eth_tp_mdix
  = link_ksettings->base.eth_tp_mdix;
 legacy_settings->eth_tp_mdix_ctrl
  = link_ksettings->base.eth_tp_mdix_ctrl;
 legacy_settings->transceiver
  = link_ksettings->base.transceiver;
 return retval;
}

/* number of 32-bit words to store the user's link mode bitmaps */
#define __ETHTOOL_LINK_MODE_MASK_NU32   \
 DIV_ROUND_UP(__ETHTOOL_LINK_MODE_MASK_NBITS, 32)

/* layout of the struct passed from/to userland */
struct ethtool_link_usettings {
 struct ethtool_link_settings base;
 struct {
  __u32 supported[__ETHTOOL_LINK_MODE_MASK_NU32];
  __u32 advertising[__ETHTOOL_LINK_MODE_MASK_NU32];
  __u32 lp_advertising[__ETHTOOL_LINK_MODE_MASK_NU32];
 } link_modes;
};

/* Internal kernel helper to query a device ethtool_link_settings. */
int __ethtool_get_link_ksettings(struct net_device *dev,
     struct ethtool_link_ksettings *link_ksettings)
{
 ASSERT_RTNL();

 if (!dev->ethtool_ops->get_link_ksettings)
  return -EOPNOTSUPP;

 if (!netif_device_present(dev))
  return -ENODEV;

 memset(link_ksettings, 0, sizeof(*link_ksettings));
 return dev->ethtool_ops->get_link_ksettings(dev, link_ksettings);
}
EXPORT_SYMBOL(__ethtool_get_link_ksettings);

/* convert ethtool_link_usettings in user space to a kernel internal
 * ethtool_link_ksettings. return 0 on success, errno on error.
 */

static int load_link_ksettings_from_user(struct ethtool_link_ksettings *to,
      const void __user *from)
{
 struct ethtool_link_usettings link_usettings;

 if (copy_from_user(&link_usettings, from, sizeof(link_usettings)))
  return -EFAULT;

 memcpy(&to->base, &link_usettings.base, sizeof(to->base));
 bitmap_from_arr32(to->link_modes.supported,
     link_usettings.link_modes.supported,
     __ETHTOOL_LINK_MODE_MASK_NBITS);
 bitmap_from_arr32(to->link_modes.advertising,
     link_usettings.link_modes.advertising,
     __ETHTOOL_LINK_MODE_MASK_NBITS);
 bitmap_from_arr32(to->link_modes.lp_advertising,
     link_usettings.link_modes.lp_advertising,
     __ETHTOOL_LINK_MODE_MASK_NBITS);

 return 0;
}

/* Check if the user is trying to change anything besides speed/duplex */
bool ethtool_virtdev_validate_cmd(const struct ethtool_link_ksettings *cmd)
{
 struct ethtool_link_settings base2 = {};

 base2.speed = cmd->base.speed;
 base2.port = PORT_OTHER;
 base2.duplex = cmd->base.duplex;
 base2.cmd = cmd->base.cmd;
 base2.link_mode_masks_nwords = cmd->base.link_mode_masks_nwords;

 return !memcmp(&base2, &cmd->base, sizeof(base2)) &&
  bitmap_empty(cmd->link_modes.supported,
        __ETHTOOL_LINK_MODE_MASK_NBITS) &&
  bitmap_empty(cmd->link_modes.lp_advertising,
        __ETHTOOL_LINK_MODE_MASK_NBITS);
}

/* convert a kernel internal ethtool_link_ksettings to
 * ethtool_link_usettings in user space. return 0 on success, errno on
 * error.
 */

static int
store_link_ksettings_for_user(void __user *to,
         const struct ethtool_link_ksettings *from)
{
 struct ethtool_link_usettings link_usettings;

 memcpy(&link_usettings, from, sizeof(link_usettings));
 bitmap_to_arr32(link_usettings.link_modes.supported,
   from->link_modes.supported,
   __ETHTOOL_LINK_MODE_MASK_NBITS);
 bitmap_to_arr32(link_usettings.link_modes.advertising,
   from->link_modes.advertising,
   __ETHTOOL_LINK_MODE_MASK_NBITS);
 bitmap_to_arr32(link_usettings.link_modes.lp_advertising,
   from->link_modes.lp_advertising,
   __ETHTOOL_LINK_MODE_MASK_NBITS);

 if (copy_to_user(to, &link_usettings, sizeof(link_usettings)))
  return -EFAULT;

 return 0;
}

/* Query device for its ethtool_link_settings. */
static int ethtool_get_link_ksettings(struct net_device *dev,
          void __user *useraddr)
{
 int err = 0;
 struct ethtool_link_ksettings link_ksettings;

 ASSERT_RTNL();
 if (!dev->ethtool_ops->get_link_ksettings)
  return -EOPNOTSUPP;

 /* handle bitmap nbits handshake */
 if (copy_from_user(&link_ksettings.base, useraddr,
      sizeof(link_ksettings.base)))
  return -EFAULT;

 if (__ETHTOOL_LINK_MODE_MASK_NU32
     != link_ksettings.base.link_mode_masks_nwords) {
  /* wrong link mode nbits requested */
  memset(&link_ksettings, 0, sizeof(link_ksettings));
  link_ksettings.base.cmd = ETHTOOL_GLINKSETTINGS;
  /* send back number of words required as negative val */
  compiletime_assert(__ETHTOOL_LINK_MODE_MASK_NU32 <= S8_MAX,
       "need too many bits for link modes!");
  link_ksettings.base.link_mode_masks_nwords
   = -((s8)__ETHTOOL_LINK_MODE_MASK_NU32);

  /* copy the base fields back to user, not the link
 * mode bitmaps
 */

  if (copy_to_user(useraddr, &link_ksettings.base,
     sizeof(link_ksettings.base)))
   return -EFAULT;

  return 0;
 }

 /* handshake successful: user/kernel agree on
 * link_mode_masks_nwords
 */


 memset(&link_ksettings, 0, sizeof(link_ksettings));
 err = dev->ethtool_ops->get_link_ksettings(dev, &link_ksettings);
 if (err < 0)
  return err;

 /* make sure we tell the right values to user */
 link_ksettings.base.cmd = ETHTOOL_GLINKSETTINGS;
 link_ksettings.base.link_mode_masks_nwords
  = __ETHTOOL_LINK_MODE_MASK_NU32;
 link_ksettings.base.master_slave_cfg = MASTER_SLAVE_CFG_UNSUPPORTED;
 link_ksettings.base.master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED;
 link_ksettings.base.rate_matching = RATE_MATCH_NONE;

 return store_link_ksettings_for_user(useraddr, &link_ksettings);
}

/* Update device ethtool_link_settings. */
static int ethtool_set_link_ksettings(struct net_device *dev,
          void __user *useraddr)
{
 struct ethtool_link_ksettings link_ksettings = {};
 int err;

 ASSERT_RTNL();

 if (!dev->ethtool_ops->set_link_ksettings)
  return -EOPNOTSUPP;

 /* make sure nbits field has expected value */
 if (copy_from_user(&link_ksettings.base, useraddr,
      sizeof(link_ksettings.base)))
  return -EFAULT;

 if (__ETHTOOL_LINK_MODE_MASK_NU32
     != link_ksettings.base.link_mode_masks_nwords)
  return -EINVAL;

 /* copy the whole structure, now that we know it has expected
 * format
 */

 err = load_link_ksettings_from_user(&link_ksettings, useraddr);
 if (err)
  return err;

 /* re-check nwords field, just in case */
 if (__ETHTOOL_LINK_MODE_MASK_NU32
     != link_ksettings.base.link_mode_masks_nwords)
  return -EINVAL;

 if (link_ksettings.base.master_slave_cfg ||
     link_ksettings.base.master_slave_state)
  return -EINVAL;

 err = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
 if (err >= 0) {
  ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF);
  ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF);
 }
 return err;
}

int ethtool_virtdev_set_link_ksettings(struct net_device *dev,
           const struct ethtool_link_ksettings *cmd,
           u32 *dev_speed, u8 *dev_duplex)
{
 u32 speed;
 u8 duplex;

 speed = cmd->base.speed;
 duplex = cmd->base.duplex;
 /* don't allow custom speed and duplex */
 if (!ethtool_validate_speed(speed) ||
     !ethtool_validate_duplex(duplex) ||
     !ethtool_virtdev_validate_cmd(cmd))
  return -EINVAL;
 *dev_speed = speed;
 *dev_duplex = duplex;

 return 0;
}
EXPORT_SYMBOL(ethtool_virtdev_set_link_ksettings);

/* Query device for its ethtool_cmd settings.
 *
 * Backward compatibility note: for compatibility with legacy ethtool, this is
 * now implemented via get_link_ksettings. When driver reports higher link mode
 * bits, a kernel warning is logged once (with name of 1st driver/device) to
 * recommend user to upgrade ethtool, but the command is successful (only the
 * lower link mode bits reported back to user). Deprecated fields from
 * ethtool_cmd (transceiver/maxrxpkt/maxtxpkt) are always set to zero.
 */

static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_link_ksettings link_ksettings;
 struct ethtool_cmd cmd;
 int err;

 ASSERT_RTNL();
 if (!dev->ethtool_ops->get_link_ksettings)
  return -EOPNOTSUPP;

 if (dev->ethtool->module_fw_flash_in_progress)
  return -EBUSY;

 memset(&link_ksettings, 0, sizeof(link_ksettings));
 err = dev->ethtool_ops->get_link_ksettings(dev, &link_ksettings);
 if (err < 0)
  return err;
 convert_link_ksettings_to_legacy_settings(&cmd, &link_ksettings);

 /* send a sensible cmd tag back to user */
 cmd.cmd = ETHTOOL_GSET;

 if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
  return -EFAULT;

 return 0;
}

/* Update device link settings with given ethtool_cmd.
 *
 * Backward compatibility note: for compatibility with legacy ethtool, this is
 * now always implemented via set_link_settings. When user's request updates
 * deprecated ethtool_cmd fields (transceiver/maxrxpkt/maxtxpkt), a kernel
 * warning is logged once (with name of 1st driver/device) to recommend user to
 * upgrade ethtool, and the request is rejected.
 */

static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_link_ksettings link_ksettings;
 struct ethtool_cmd cmd;
 int ret;

 ASSERT_RTNL();

 if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
  return -EFAULT;
 if (!dev->ethtool_ops->set_link_ksettings)
  return -EOPNOTSUPP;

 if (!convert_legacy_settings_to_link_ksettings(&link_ksettings, &cmd))
  return -EINVAL;
 link_ksettings.base.link_mode_masks_nwords =
  __ETHTOOL_LINK_MODE_MASK_NU32;
 ret = dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings);
 if (ret >= 0) {
  ethtool_notify(dev, ETHTOOL_MSG_LINKINFO_NTF);
  ethtool_notify(dev, ETHTOOL_MSG_LINKMODES_NTF);
 }
 return ret;
}

static int
ethtool_get_drvinfo(struct net_device *dev, struct ethtool_devlink_compat *rsp)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct device *parent = dev->dev.parent;

 rsp->info.cmd = ETHTOOL_GDRVINFO;
 strscpy(rsp->info.version, init_uts_ns.name.release,
  sizeof(rsp->info.version));
 if (ops->get_drvinfo) {
  ops->get_drvinfo(dev, &rsp->info);
  if (!rsp->info.bus_info[0] && parent)
   strscpy(rsp->info.bus_info, dev_name(parent),
    sizeof(rsp->info.bus_info));
  if (!rsp->info.driver[0] && parent && parent->driver)
   strscpy(rsp->info.driver, parent->driver->name,
    sizeof(rsp->info.driver));
 } else if (parent && parent->driver) {
  strscpy(rsp->info.bus_info, dev_name(parent),
   sizeof(rsp->info.bus_info));
  strscpy(rsp->info.driver, parent->driver->name,
   sizeof(rsp->info.driver));
 } else if (dev->rtnl_link_ops) {
  strscpy(rsp->info.driver, dev->rtnl_link_ops->kind,
   sizeof(rsp->info.driver));
 } else {
  return -EOPNOTSUPP;
 }

 /*
 * this method of obtaining string set info is deprecated;
 * Use ETHTOOL_GSSET_INFO instead.
 */

 if (ops->get_sset_count) {
  int rc;

  rc = ops->get_sset_count(dev, ETH_SS_TEST);
  if (rc >= 0)
   rsp->info.testinfo_len = rc;
  rc = ops->get_sset_count(dev, ETH_SS_STATS);
  if (rc >= 0)
   rsp->info.n_stats = rc;
  rc = ops->get_sset_count(dev, ETH_SS_PRIV_FLAGS);
  if (rc >= 0)
   rsp->info.n_priv_flags = rc;
 }
 if (ops->get_regs_len) {
  int ret = ops->get_regs_len(dev);

  if (ret > 0)
   rsp->info.regdump_len = ret;
 }

 if (ops->get_eeprom_len)
  rsp->info.eedump_len = ops->get_eeprom_len(dev);

 if (!rsp->info.fw_version[0])
  rsp->devlink = netdev_to_devlink_get(dev);

 return 0;
}

static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,
          void __user *useraddr)
{
 struct ethtool_sset_info info;
 u64 sset_mask;
 int i, idx = 0, n_bits = 0, ret, rc;
 u32 *info_buf = NULL;

 if (copy_from_user(&info, useraddr, sizeof(info)))
  return -EFAULT;

 /* store copy of mask, because we zero struct later on */
 sset_mask = info.sset_mask;
 if (!sset_mask)
  return 0;

 /* calculate size of return buffer */
 n_bits = hweight64(sset_mask);

 memset(&info, 0, sizeof(info));
 info.cmd = ETHTOOL_GSSET_INFO;

 info_buf = kcalloc(n_bits, sizeof(u32), GFP_USER);
 if (!info_buf)
  return -ENOMEM;

 /*
 * fill return buffer based on input bitmask and successful
 * get_sset_count return
 */

 for (i = 0; i < 64; i++) {
  if (!(sset_mask & (1ULL << i)))
   continue;

  rc = __ethtool_get_sset_count(dev, i);
  if (rc >= 0) {
   info.sset_mask |= (1ULL << i);
   info_buf[idx++] = rc;
  }
 }

 ret = -EFAULT;
 if (copy_to_user(useraddr, &info, sizeof(info)))
  goto out;

 useraddr += offsetof(struct ethtool_sset_info, data);
 if (copy_to_user(useraddr, info_buf, array_size(idx, sizeof(u32))))
  goto out;

 ret = 0;

out:
 kfree(info_buf);
 return ret;
}

static noinline_for_stack int
ethtool_rxnfc_copy_from_compat(struct ethtool_rxnfc *rxnfc,
          const struct compat_ethtool_rxnfc __user *useraddr,
          size_t size)
{
 struct compat_ethtool_rxnfc crxnfc = {};

 /* We expect there to be holes between fs.m_ext and
 * fs.ring_cookie and at the end of fs, but nowhere else.
 * On non-x86, no conversion should be needed.
 */

 BUILD_BUG_ON(!IS_ENABLED(CONFIG_X86_64) &&
       sizeof(struct compat_ethtool_rxnfc) !=
       sizeof(struct ethtool_rxnfc));
 BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) +
       sizeof(useraddr->fs.m_ext) !=
       offsetof(struct ethtool_rxnfc, fs.m_ext) +
       sizeof(rxnfc->fs.m_ext));
 BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.location) -
       offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) !=
       offsetof(struct ethtool_rxnfc, fs.location) -
       offsetof(struct ethtool_rxnfc, fs.ring_cookie));

 if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc))))
  return -EFAULT;

 *rxnfc = (struct ethtool_rxnfc) {
  .cmd  = crxnfc.cmd,
  .flow_type = crxnfc.flow_type,
  .data  = crxnfc.data,
  .fs  = {
   .flow_type = crxnfc.fs.flow_type,
   .h_u  = crxnfc.fs.h_u,
   .h_ext  = crxnfc.fs.h_ext,
   .m_u  = crxnfc.fs.m_u,
   .m_ext  = crxnfc.fs.m_ext,
   .ring_cookie = crxnfc.fs.ring_cookie,
   .location = crxnfc.fs.location,
  },
  .rule_cnt = crxnfc.rule_cnt,
 };

 return 0;
}

static int ethtool_rxnfc_copy_from_user(struct ethtool_rxnfc *rxnfc,
     const void __user *useraddr,
     size_t size)
{
 if (compat_need_64bit_alignment_fixup())
  return ethtool_rxnfc_copy_from_compat(rxnfc, useraddr, size);

 if (copy_from_user(rxnfc, useraddr, size))
  return -EFAULT;

 return 0;
}

static int ethtool_rxnfc_copy_to_compat(void __user *useraddr,
     const struct ethtool_rxnfc *rxnfc,
     size_t size, const u32 *rule_buf)
{
 struct compat_ethtool_rxnfc crxnfc;

 memset(&crxnfc, 0, sizeof(crxnfc));
 crxnfc = (struct compat_ethtool_rxnfc) {
  .cmd  = rxnfc->cmd,
  .flow_type = rxnfc->flow_type,
  .data  = rxnfc->data,
  .fs  = {
   .flow_type = rxnfc->fs.flow_type,
   .h_u  = rxnfc->fs.h_u,
   .h_ext  = rxnfc->fs.h_ext,
   .m_u  = rxnfc->fs.m_u,
   .m_ext  = rxnfc->fs.m_ext,
   .ring_cookie = rxnfc->fs.ring_cookie,
   .location = rxnfc->fs.location,
  },
  .rule_cnt = rxnfc->rule_cnt,
 };

 if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc))))
  return -EFAULT;

 return 0;
}

static int ethtool_rxnfc_copy_struct(u32 cmd, struct ethtool_rxnfc *info,
         size_t *info_size, void __user *useraddr)
{
 /* struct ethtool_rxnfc was originally defined for
 * ETHTOOL_{G,S}RXFH with only the cmd, flow_type and data
 * members.  User-space might still be using that
 * definition.
 */

 if (cmd == ETHTOOL_GRXFH || cmd == ETHTOOL_SRXFH)
  *info_size = (offsetof(struct ethtool_rxnfc, data) +
         sizeof(info->data));

 if (ethtool_rxnfc_copy_from_user(info, useraddr, *info_size))
  return -EFAULT;

 if ((cmd == ETHTOOL_GRXFH || cmd == ETHTOOL_SRXFH) && info->flow_type & FLOW_RSS) {
  *info_size = sizeof(*info);
  if (ethtool_rxnfc_copy_from_user(info, useraddr, *info_size))
   return -EFAULT;
  /* Since malicious users may modify the original data,
 * we need to check whether FLOW_RSS is still requested.
 */

  if (!(info->flow_type & FLOW_RSS))
   return -EINVAL;
 }

 if (info->cmd != cmd)
  return -EINVAL;

 return 0;
}

static int ethtool_rxnfc_copy_to_user(void __user *useraddr,
          const struct ethtool_rxnfc *rxnfc,
          size_t size, const u32 *rule_buf)
{
 int ret;

 if (compat_need_64bit_alignment_fixup()) {
  ret = ethtool_rxnfc_copy_to_compat(useraddr, rxnfc, size,
         rule_buf);
  useraddr += offsetof(struct compat_ethtool_rxnfc, rule_locs);
 } else {
  ret = copy_to_user(useraddr, rxnfc, size);
  useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
 }

 if (ret)
  return -EFAULT;

 if (rule_buf) {
  if (copy_to_user(useraddr, rule_buf,
     rxnfc->rule_cnt * sizeof(u32)))
   return -EFAULT;
 }

 return 0;
}

static bool flow_type_hashable(u32 flow_type)
{
 switch (flow_type) {
 case ETHER_FLOW:
 case TCP_V4_FLOW:
 case UDP_V4_FLOW:
 case SCTP_V4_FLOW:
 case AH_ESP_V4_FLOW:
 case TCP_V6_FLOW:
 case UDP_V6_FLOW:
 case SCTP_V6_FLOW:
 case AH_ESP_V6_FLOW:
 case AH_V4_FLOW:
 case ESP_V4_FLOW:
 case AH_V6_FLOW:
 case ESP_V6_FLOW:
 case IPV4_FLOW:
 case IPV6_FLOW:
 case GTPU_V4_FLOW:
 case GTPU_V6_FLOW:
 case GTPC_V4_FLOW:
 case GTPC_V6_FLOW:
 case GTPC_TEID_V4_FLOW:
 case GTPC_TEID_V6_FLOW:
 case GTPU_EH_V4_FLOW:
 case GTPU_EH_V6_FLOW:
 case GTPU_UL_V4_FLOW:
 case GTPU_UL_V6_FLOW:
 case GTPU_DL_V4_FLOW:
 case GTPU_DL_V6_FLOW:
  return true;
 }

 return false;
}

/* When adding a new type, update the assert and, if it's hashable, add it to
 * the flow_type_hashable switch case.
 */

static_assert(GTPU_DL_V6_FLOW + 1 == __FLOW_TYPE_COUNT);

static int ethtool_check_xfrm_rxfh(u32 input_xfrm, u64 rxfh)
{
 /* Sanity check: if symmetric-xor/symmetric-or-xor is set, then:
 * 1 - no other fields besides IP src/dst and/or L4 src/dst are set
 * 2 - If src is set, dst must also be set
 */

 if ((input_xfrm != RXH_XFRM_NO_CHANGE &&
      input_xfrm & (RXH_XFRM_SYM_XOR | RXH_XFRM_SYM_OR_XOR)) &&
     !ethtool_rxfh_config_is_sym(rxfh))
  return -EINVAL;

 return 0;
}

static int ethtool_check_flow_types(struct net_device *dev, u32 input_xfrm)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 int err;
 u32 i;

 if (!input_xfrm || input_xfrm == RXH_XFRM_NO_CHANGE)
  return 0;

 for (i = 0; i < __FLOW_TYPE_COUNT; i++) {
  struct ethtool_rxfh_fields fields = {
   .flow_type = i,
  };

  if (!flow_type_hashable(i))
   continue;

  if (ops->get_rxfh_fields(dev, &fields))
   continue;

  err = ethtool_check_xfrm_rxfh(input_xfrm, fields.data);
  if (err)
   return err;
 }

 return 0;
}

static noinline_for_stack int
ethtool_set_rxfh_fields(struct net_device *dev, u32 cmd, void __user *useraddr)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct ethtool_rxfh_fields fields = {};
 struct ethtool_rxnfc info;
 size_t info_size = sizeof(info);
 int rc;

 if (!ops->set_rxfh_fields)
  return -EOPNOTSUPP;

 rc = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr);
 if (rc)
  return rc;

 if (info.flow_type & FLOW_RSS && info.rss_context &&
     !ops->rxfh_per_ctx_fields)
  return -EINVAL;

 mutex_lock(&dev->ethtool->rss_lock);
 if (ops->get_rxfh) {
  struct ethtool_rxfh_param rxfh = {};

  rc = ops->get_rxfh(dev, &rxfh);
  if (rc)
   goto exit_unlock;

  rc = ethtool_check_xfrm_rxfh(rxfh.input_xfrm, info.data);
  if (rc)
   goto exit_unlock;
 }

 fields.data = info.data;
 fields.flow_type = info.flow_type & ~FLOW_RSS;
 if (info.flow_type & FLOW_RSS)
  fields.rss_context = info.rss_context;

 rc = ops->set_rxfh_fields(dev, &fields, NULL);
exit_unlock:
 mutex_unlock(&dev->ethtool->rss_lock);
 if (rc)
  return rc;

 ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, fields.rss_context);
 return 0;
}

static noinline_for_stack int
ethtool_get_rxfh_fields(struct net_device *dev, u32 cmd, void __user *useraddr)
{
 struct ethtool_rxnfc info;
 size_t info_size = sizeof(info);
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct ethtool_rxfh_fields fields = {};
 int ret;

 if (!ops->get_rxfh_fields)
  return -EOPNOTSUPP;

 ret = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr);
 if (ret)
  return ret;

 if (info.flow_type & FLOW_RSS && info.rss_context &&
     !ops->rxfh_per_ctx_fields)
  return -EINVAL;

 fields.flow_type = info.flow_type & ~FLOW_RSS;
 if (info.flow_type & FLOW_RSS)
  fields.rss_context = info.rss_context;

 mutex_lock(&dev->ethtool->rss_lock);
 ret = ops->get_rxfh_fields(dev, &fields);
 mutex_unlock(&dev->ethtool->rss_lock);
 if (ret < 0)
  return ret;

 info.data = fields.data;

 return ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL);
}

static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
      u32 cmd, void __user *useraddr)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct ethtool_rxnfc info;
 size_t info_size = sizeof(info);
 int rc;

 if (!ops->set_rxnfc)
  return -EOPNOTSUPP;

 rc = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr);
 if (rc)
  return rc;

 if (cmd == ETHTOOL_SRXCLSRLINS && info.fs.flow_type & FLOW_RSS) {
  /* Nonzero ring with RSS only makes sense
 * if NIC adds them together
 */

  if (!ops->cap_rss_rxnfc_adds &&
      ethtool_get_flow_spec_ring(info.fs.ring_cookie))
   return -EINVAL;

  if (info.rss_context &&
      !xa_load(&dev->ethtool->rss_ctx, info.rss_context))
   return -EINVAL;
 }

 rc = ops->set_rxnfc(dev, &info);
 if (rc)
  return rc;

 if (cmd == ETHTOOL_SRXCLSRLINS &&
     ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL))
  return -EFAULT;

 return 0;
}

static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
      u32 cmd, void __user *useraddr)
{
 struct ethtool_rxnfc info;
 size_t info_size = sizeof(info);
 const struct ethtool_ops *ops = dev->ethtool_ops;
 int ret;
 void *rule_buf = NULL;

 if (!ops->get_rxnfc)
  return -EOPNOTSUPP;

 ret = ethtool_rxnfc_copy_struct(cmd, &info, &info_size, useraddr);
 if (ret)
  return ret;

 if (info.cmd == ETHTOOL_GRXCLSRLALL) {
  if (info.rule_cnt > 0) {
   if (info.rule_cnt <= KMALLOC_MAX_SIZE / sizeof(u32))
    rule_buf = kcalloc(info.rule_cnt, sizeof(u32),
         GFP_USER);
   if (!rule_buf)
    return -ENOMEM;
  }
 }

 ret = ops->get_rxnfc(dev, &info, rule_buf);
 if (ret < 0)
  goto err_out;

 ret = ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, rule_buf);
err_out:
 kfree(rule_buf);

 return ret;
}

static int ethtool_copy_validate_indir(u32 *indir, void __user *useraddr,
     struct ethtool_rxnfc *rx_rings,
     u32 size)
{
 int i;

 if (copy_from_user(indir, useraddr, array_size(size, sizeof(indir[0]))))
  return -EFAULT;

 /* Validate ring indices */
 for (i = 0; i < size; i++)
  if (indir[i] >= rx_rings->data)
   return -EINVAL;

 return 0;
}

u8 netdev_rss_key[NETDEV_RSS_KEY_LEN] __read_mostly;

void netdev_rss_key_fill(void *buffer, size_t len)
{
 BUG_ON(len > sizeof(netdev_rss_key));
 net_get_random_once(netdev_rss_key, sizeof(netdev_rss_key));
 memcpy(buffer, netdev_rss_key, len);
}
EXPORT_SYMBOL(netdev_rss_key_fill);

static noinline_for_stack int ethtool_get_rxfh_indir(struct net_device *dev,
           void __user *useraddr)
{
 struct ethtool_rxfh_param rxfh = {};
 u32 user_size;
 int ret;

 if (!dev->ethtool_ops->get_rxfh_indir_size ||
     !dev->ethtool_ops->get_rxfh)
  return -EOPNOTSUPP;
 rxfh.indir_size = dev->ethtool_ops->get_rxfh_indir_size(dev);
 if (rxfh.indir_size == 0)
  return -EOPNOTSUPP;

 if (copy_from_user(&user_size,
      useraddr + offsetof(struct ethtool_rxfh_indir, size),
      sizeof(user_size)))
  return -EFAULT;

 if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh_indir, size),
    &rxfh.indir_size, sizeof(rxfh.indir_size)))
  return -EFAULT;

 /* If the user buffer size is 0, this is just a query for the
 * device table size.  Otherwise, if it's smaller than the
 * device table size it's an error.
 */

 if (user_size < rxfh.indir_size)
  return user_size == 0 ? 0 : -EINVAL;

 rxfh.indir = kcalloc(rxfh.indir_size, sizeof(rxfh.indir[0]), GFP_USER);
 if (!rxfh.indir)
  return -ENOMEM;

 mutex_lock(&dev->ethtool->rss_lock);
 ret = dev->ethtool_ops->get_rxfh(dev, &rxfh);
 mutex_unlock(&dev->ethtool->rss_lock);
 if (ret)
  goto out;
 if (copy_to_user(useraddr +
    offsetof(struct ethtool_rxfh_indir, ring_index[0]),
    rxfh.indir, rxfh.indir_size * sizeof(*rxfh.indir)))
  ret = -EFAULT;

out:
 kfree(rxfh.indir);
 return ret;
}

static noinline_for_stack int ethtool_set_rxfh_indir(struct net_device *dev,
           void __user *useraddr)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct ethtool_rxfh_param rxfh_dev = {};
 struct netlink_ext_ack *extack = NULL;
 struct ethtool_rxnfc rx_rings;
 u32 user_size, i;
 int ret;
 u32 ringidx_offset = offsetof(struct ethtool_rxfh_indir, ring_index[0]);

 if (!ops->get_rxfh_indir_size || !ops->set_rxfh ||
     !ops->get_rxnfc)
  return -EOPNOTSUPP;

 rxfh_dev.indir_size = ops->get_rxfh_indir_size(dev);
 if (rxfh_dev.indir_size == 0)
  return -EOPNOTSUPP;

 if (copy_from_user(&user_size,
      useraddr + offsetof(struct ethtool_rxfh_indir, size),
      sizeof(user_size)))
  return -EFAULT;

 if (user_size != 0 && user_size != rxfh_dev.indir_size)
  return -EINVAL;

 rxfh_dev.indir = kcalloc(rxfh_dev.indir_size,
     sizeof(rxfh_dev.indir[0]), GFP_USER);
 if (!rxfh_dev.indir)
  return -ENOMEM;

 rx_rings.cmd = ETHTOOL_GRXRINGS;
 ret = ops->get_rxnfc(dev, &rx_rings, NULL);
 if (ret)
  goto out;

 if (user_size == 0) {
  u32 *indir = rxfh_dev.indir;

  for (i = 0; i < rxfh_dev.indir_size; i++)
   indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data);
 } else {
  ret = ethtool_copy_validate_indir(rxfh_dev.indir,
        useraddr + ringidx_offset,
        &rx_rings,
        rxfh_dev.indir_size);
  if (ret)
   goto out;
 }

 rxfh_dev.hfunc = ETH_RSS_HASH_NO_CHANGE;

 mutex_lock(&dev->ethtool->rss_lock);
 ret = ops->set_rxfh(dev, &rxfh_dev, extack);
 if (ret)
  goto out_unlock;

 /* indicate whether rxfh was set to default */
 if (user_size == 0)
  dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
 else
  dev->priv_flags |= IFF_RXFH_CONFIGURED;

out_unlock:
 mutex_unlock(&dev->ethtool->rss_lock);
out:
 kfree(rxfh_dev.indir);
 return ret;
}

static noinline_for_stack int ethtool_get_rxfh(struct net_device *dev,
            void __user *useraddr)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;
 struct ethtool_rxfh_param rxfh_dev = {};
 u32 user_indir_size, user_key_size;
 struct ethtool_rxfh_context *ctx;
 struct ethtool_rxfh rxfh;
 u32 indir_bytes;
 u8 *rss_config;
 u32 total_size;
 int ret;

 if (!ops->get_rxfh)
  return -EOPNOTSUPP;

 if (ops->get_rxfh_indir_size)
  rxfh_dev.indir_size = ops->get_rxfh_indir_size(dev);
 if (ops->get_rxfh_key_size)
  rxfh_dev.key_size = ops->get_rxfh_key_size(dev);

 if (copy_from_user(&rxfh, useraddr, sizeof(rxfh)))
  return -EFAULT;
 user_indir_size = rxfh.indir_size;
 user_key_size = rxfh.key_size;

 /* Check that reserved fields are 0 for now */
 if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd32)
  return -EINVAL;
 /* Most drivers don't handle rss_context, check it's 0 as well */
 if (rxfh.rss_context && !ops->create_rxfh_context)
  return -EOPNOTSUPP;

 rxfh.indir_size = rxfh_dev.indir_size;
 rxfh.key_size = rxfh_dev.key_size;
 if (copy_to_user(useraddr, &rxfh, sizeof(rxfh)))
  return -EFAULT;

 if ((user_indir_size && user_indir_size != rxfh_dev.indir_size) ||
     (user_key_size && user_key_size != rxfh_dev.key_size))
  return -EINVAL;

 indir_bytes = user_indir_size * sizeof(rxfh_dev.indir[0]);
 total_size = indir_bytes + user_key_size;
 rss_config = kzalloc(total_size, GFP_USER);
 if (!rss_config)
  return -ENOMEM;

 if (user_indir_size)
  rxfh_dev.indir = (u32 *)rss_config;

 if (user_key_size)
  rxfh_dev.key = rss_config + indir_bytes;

 mutex_lock(&dev->ethtool->rss_lock);
 if (rxfh.rss_context) {
  ctx = xa_load(&dev->ethtool->rss_ctx, rxfh.rss_context);
  if (!ctx) {
   ret = -ENOENT;
   goto out;
  }
  if (rxfh_dev.indir)
   memcpy(rxfh_dev.indir, ethtool_rxfh_context_indir(ctx),
          indir_bytes);
  if (!ops->rxfh_per_ctx_key) {
   rxfh_dev.key_size = 0;
  } else {
   if (rxfh_dev.key)
    memcpy(rxfh_dev.key,
           ethtool_rxfh_context_key(ctx),
           user_key_size);
   rxfh_dev.hfunc = ctx->hfunc;
  }
  rxfh_dev.input_xfrm = ctx->input_xfrm;
  ret = 0;
 } else {
  ret = dev->ethtool_ops->get_rxfh(dev, &rxfh_dev);
  if (ret)
   goto out;
 }

 if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, hfunc),
    &rxfh_dev.hfunc, sizeof(rxfh.hfunc))) {
  ret = -EFAULT;
 } else if (copy_to_user(useraddr +
    offsetof(struct ethtool_rxfh, input_xfrm),
    &rxfh_dev.input_xfrm,
    sizeof(rxfh.input_xfrm))) {
  ret = -EFAULT;
 } else if (copy_to_user(useraddr +
    offsetof(struct ethtool_rxfh, key_size),
    &rxfh_dev.key_size,
    sizeof(rxfh.key_size))) {
  ret = -EFAULT;
 } else if (copy_to_user(useraddr +
         offsetof(struct ethtool_rxfh, rss_config[0]),
         rss_config, total_size)) {
  ret = -EFAULT;
 }
out:
 mutex_unlock(&dev->ethtool->rss_lock);
 kfree(rss_config);

 return ret;
}

static noinline_for_stack int ethtool_set_rxfh(struct net_device *dev,
            void __user *useraddr)
{
 u32 rss_cfg_offset = offsetof(struct ethtool_rxfh, rss_config[0]);
 const struct ethtool_ops *ops = dev->ethtool_ops;
 u32 dev_indir_size = 0, dev_key_size = 0, i;
 u32 user_indir_len = 0, indir_bytes = 0;
 struct ethtool_rxfh_param rxfh_dev = {};
 struct ethtool_rxfh_context *ctx = NULL;
 struct netlink_ext_ack *extack = NULL;
 struct ethtool_rxnfc rx_rings;
 struct ethtool_rxfh rxfh;
 bool create = false;
 u8 *rss_config;
 int ntf = 0;
 int ret;

 if (!ops->get_rxnfc || !ops->set_rxfh)
  return -EOPNOTSUPP;

 if (ops->get_rxfh_indir_size)
  dev_indir_size = ops->get_rxfh_indir_size(dev);
 if (ops->get_rxfh_key_size)
  dev_key_size = ops->get_rxfh_key_size(dev);

 if (copy_from_user(&rxfh, useraddr, sizeof(rxfh)))
  return -EFAULT;

 /* Check that reserved fields are 0 for now */
 if (rxfh.rsvd8[0] || rxfh.rsvd8[1] || rxfh.rsvd32)
  return -EINVAL;
 /* Most drivers don't handle rss_context, check it's 0 as well */
 if (rxfh.rss_context && !ops->create_rxfh_context)
  return -EOPNOTSUPP;
 /* Check input data transformation capabilities */
 if (rxfh.input_xfrm && rxfh.input_xfrm != RXH_XFRM_SYM_XOR &&
     rxfh.input_xfrm != RXH_XFRM_SYM_OR_XOR &&
     rxfh.input_xfrm != RXH_XFRM_NO_CHANGE)
  return -EINVAL;
 if (rxfh.input_xfrm != RXH_XFRM_NO_CHANGE &&
     rxfh.input_xfrm & ~ops->supported_input_xfrm)
  return -EOPNOTSUPP;
 create = rxfh.rss_context == ETH_RXFH_CONTEXT_ALLOC;

 if ((rxfh.indir_size &&
      rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE &&
      rxfh.indir_size != dev_indir_size) ||
     (rxfh.key_size && rxfh.key_size != dev_key_size))
  return -EINVAL;

 /* Must request at least one change: indir size, hash key, function
 * or input transformation.
 * There's no need for any of it in case of context creation.
 */

 if (!create &&
     (rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE &&
      rxfh.key_size == 0 && rxfh.hfunc == ETH_RSS_HASH_NO_CHANGE &&
      rxfh.input_xfrm == RXH_XFRM_NO_CHANGE))
  return -EINVAL;

 indir_bytes = dev_indir_size * sizeof(rxfh_dev.indir[0]);

 /* Check settings which may be global rather than per RSS-context */
 if (rxfh.rss_context && !ops->rxfh_per_ctx_key)
  if (rxfh.key_size ||
      (rxfh.hfunc && rxfh.hfunc != ETH_RSS_HASH_NO_CHANGE) ||
      (rxfh.input_xfrm && rxfh.input_xfrm != RXH_XFRM_NO_CHANGE))
   return -EOPNOTSUPP;

 rss_config = kzalloc(indir_bytes + dev_key_size, GFP_USER);
 if (!rss_config)
  return -ENOMEM;

 rx_rings.cmd = ETHTOOL_GRXRINGS;
 ret = ops->get_rxnfc(dev, &rx_rings, NULL);
 if (ret)
  goto out_free;

 /* rxfh.indir_size == 0 means reset the indir table to default (master
 * context) or delete the context (other RSS contexts).
 * rxfh.indir_size == ETH_RXFH_INDIR_NO_CHANGE means leave it unchanged.
 */

 if (rxfh.indir_size &&
     rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE) {
  user_indir_len = indir_bytes;
  rxfh_dev.indir = (u32 *)rss_config;
  rxfh_dev.indir_size = dev_indir_size;
  ret = ethtool_copy_validate_indir(rxfh_dev.indir,
        useraddr + rss_cfg_offset,
        &rx_rings,
        rxfh.indir_size);
  if (ret)
   goto out_free;
 } else if (rxfh.indir_size == 0) {
  if (rxfh.rss_context == 0) {
   u32 *indir;

   rxfh_dev.indir = (u32 *)rss_config;
   rxfh_dev.indir_size = dev_indir_size;
   indir = rxfh_dev.indir;
   for (i = 0; i < dev_indir_size; i++)
    indir[i] = ethtool_rxfh_indir_default(i, rx_rings.data);
  } else {
   rxfh_dev.rss_delete = true;
  }
 }

 if (rxfh.key_size) {
  rxfh_dev.key_size = dev_key_size;
  rxfh_dev.key = rss_config + indir_bytes;
  if (copy_from_user(rxfh_dev.key,
       useraddr + rss_cfg_offset + user_indir_len,
       rxfh.key_size)) {
   ret = -EFAULT;
   goto out_free;
  }
 }

 mutex_lock(&dev->ethtool->rss_lock);

 ret = ethtool_check_flow_types(dev, rxfh.input_xfrm);
 if (ret)
  goto out_unlock;

 if (rxfh.rss_context && rxfh_dev.rss_delete) {
  ret = ethtool_check_rss_ctx_busy(dev, rxfh.rss_context);
  if (ret)
   goto out_unlock;
 }

 if (create) {
  u32 limit, ctx_id;

  if (rxfh_dev.rss_delete) {
   ret = -EINVAL;
   goto out_unlock;
  }
  ctx = ethtool_rxfh_ctx_alloc(ops, dev_indir_size, dev_key_size);
  if (!ctx) {
   ret = -ENOMEM;
   goto out_unlock;
  }

  limit = ops->rxfh_max_num_contexts ?: U32_MAX;
  ret = xa_alloc(&dev->ethtool->rss_ctx, &ctx_id, ctx,
          XA_LIMIT(1, limit - 1), GFP_KERNEL_ACCOUNT);
  if (ret < 0) {
   kfree(ctx);
   goto out_unlock;
  }
  WARN_ON(!ctx_id); /* can't happen */
  rxfh.rss_context = ctx_id;
 } else if (rxfh.rss_context) {
  ctx = xa_load(&dev->ethtool->rss_ctx, rxfh.rss_context);
  if (!ctx) {
   ret = -ENOENT;
   goto out_unlock;
  }
 }
 rxfh_dev.hfunc = rxfh.hfunc;
 rxfh_dev.rss_context = rxfh.rss_context;
 rxfh_dev.input_xfrm = rxfh.input_xfrm;

 if (!rxfh.rss_context) {
  ntf = ETHTOOL_MSG_RSS_NTF;
  ret = ops->set_rxfh(dev, &rxfh_dev, extack);
 } else if (create) {
  ntf = ETHTOOL_MSG_RSS_CREATE_NTF;
  ret = ops->create_rxfh_context(dev, ctx, &rxfh_dev, extack);
  /* Make sure driver populates defaults */
  WARN_ON_ONCE(!ret && !rxfh_dev.key && ops->rxfh_per_ctx_key &&
        !memchr_inv(ethtool_rxfh_context_key(ctx), 0,
      ctx->key_size));
 } else if (rxfh_dev.rss_delete) {
  ntf = ETHTOOL_MSG_RSS_DELETE_NTF;
  ret = ops->remove_rxfh_context(dev, ctx, rxfh.rss_context,
            extack);
 } else {
  ntf = ETHTOOL_MSG_RSS_NTF;
  ret = ops->modify_rxfh_context(dev, ctx, &rxfh_dev, extack);
 }
 if (ret) {
  ntf = 0;
  if (create) {
   /* failed to create, free our new tracking entry */
   xa_erase(&dev->ethtool->rss_ctx, rxfh.rss_context);
   kfree(ctx);
  }
  goto out_unlock;
 }

 if (copy_to_user(useraddr + offsetof(struct ethtool_rxfh, rss_context),
    &rxfh_dev.rss_context, sizeof(rxfh_dev.rss_context)))
  ret = -EFAULT;

 if (!rxfh_dev.rss_context) {
  /* indicate whether rxfh was set to default */
  if (rxfh.indir_size == 0)
   dev->priv_flags &= ~IFF_RXFH_CONFIGURED;
  else if (rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE)
   dev->priv_flags |= IFF_RXFH_CONFIGURED;
 }
 /* Update rss_ctx tracking */
 if (rxfh_dev.rss_delete) {
  WARN_ON(xa_erase(&dev->ethtool->rss_ctx, rxfh.rss_context) != ctx);
  kfree(ctx);
 } else if (ctx) {
  if (rxfh_dev.indir) {
   for (i = 0; i < dev_indir_size; i++)
    ethtool_rxfh_context_indir(ctx)[i] = rxfh_dev.indir[i];
   ctx->indir_configured =
    rxfh.indir_size &&
    rxfh.indir_size != ETH_RXFH_INDIR_NO_CHANGE;
  }
  if (rxfh_dev.key) {
   memcpy(ethtool_rxfh_context_key(ctx), rxfh_dev.key,
          dev_key_size);
   ctx->key_configured = !!rxfh.key_size;
  }
  if (rxfh_dev.hfunc != ETH_RSS_HASH_NO_CHANGE)
   ctx->hfunc = rxfh_dev.hfunc;
  if (rxfh_dev.input_xfrm != RXH_XFRM_NO_CHANGE)
   ctx->input_xfrm = rxfh_dev.input_xfrm;
 }

out_unlock:
 mutex_unlock(&dev->ethtool->rss_lock);
out_free:
 kfree(rss_config);
 if (ntf)
  ethtool_rss_notify(dev, ntf, rxfh.rss_context);
 return ret;
}

static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_regs regs;
 const struct ethtool_ops *ops = dev->ethtool_ops;
 void *regbuf;
 int reglen, ret;

 if (!ops->get_regs || !ops->get_regs_len)
  return -EOPNOTSUPP;

 if (copy_from_user(®s, useraddr, sizeof(regs)))
  return -EFAULT;

 reglen = ops->get_regs_len(dev);
 if (reglen <= 0)
  return reglen;

 if (regs.len > reglen)
  regs.len = reglen;

 regbuf = vzalloc(reglen);
 if (!regbuf)
  return -ENOMEM;

 if (regs.len < reglen)
  reglen = regs.len;

 ops->get_regs(dev, ®s, regbuf);

 ret = -EFAULT;
 if (copy_to_user(useraddr, ®s, sizeof(regs)))
  goto out;
 useraddr += offsetof(struct ethtool_regs, data);
 if (copy_to_user(useraddr, regbuf, reglen))
  goto out;
 ret = 0;

 out:
 vfree(regbuf);
 return ret;
}

static int ethtool_reset(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_value reset;
 int ret;

 if (!dev->ethtool_ops->reset)
  return -EOPNOTSUPP;

 if (dev->ethtool->module_fw_flash_in_progress)
  return -EBUSY;

 if (copy_from_user(&reset, useraddr, sizeof(reset)))
  return -EFAULT;

 ret = dev->ethtool_ops->reset(dev, &reset.data);
 if (ret)
  return ret;

 if (copy_to_user(useraddr, &reset, sizeof(reset)))
  return -EFAULT;
 return 0;
}

static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_wolinfo wol;

 if (!dev->ethtool_ops->get_wol)
  return -EOPNOTSUPP;

 memset(&wol, 0, sizeof(struct ethtool_wolinfo));
 wol.cmd = ETHTOOL_GWOL;
 dev->ethtool_ops->get_wol(dev, &wol);

 if (copy_to_user(useraddr, &wol, sizeof(wol)))
  return -EFAULT;
 return 0;
}

static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_wolinfo wol, cur_wol;
 int ret;

 if (!dev->ethtool_ops->get_wol || !dev->ethtool_ops->set_wol)
  return -EOPNOTSUPP;

 memset(&cur_wol, 0, sizeof(struct ethtool_wolinfo));
 cur_wol.cmd = ETHTOOL_GWOL;
 dev->ethtool_ops->get_wol(dev, &cur_wol);

 if (copy_from_user(&wol, useraddr, sizeof(wol)))
  return -EFAULT;

 if (wol.wolopts & ~cur_wol.supported)
  return -EINVAL;

 if (wol.wolopts == cur_wol.wolopts &&
     !memcmp(wol.sopass, cur_wol.sopass, sizeof(wol.sopass)))
  return 0;

 ret = dev->ethtool_ops->set_wol(dev, &wol);
 if (ret)
  return ret;

 dev->ethtool->wol_enabled = !!wol.wolopts;
 ethtool_notify(dev, ETHTOOL_MSG_WOL_NTF);

 return 0;
}

static void eee_to_keee(struct ethtool_keee *keee,
   const struct ethtool_eee *eee)
{
 memset(keee, 0, sizeof(*keee));

 keee->eee_enabled = eee->eee_enabled;
 keee->tx_lpi_enabled = eee->tx_lpi_enabled;
 keee->tx_lpi_timer = eee->tx_lpi_timer;

 ethtool_convert_legacy_u32_to_link_mode(keee->advertised,
      eee->advertised);
}

static void keee_to_eee(struct ethtool_eee *eee,
   const struct ethtool_keee *keee)
{
 bool overflow;

 memset(eee, 0, sizeof(*eee));

 eee->eee_active = keee->eee_active;
 eee->eee_enabled = keee->eee_enabled;
 eee->tx_lpi_enabled = keee->tx_lpi_enabled;
 eee->tx_lpi_timer = keee->tx_lpi_timer;

 overflow = !ethtool_convert_link_mode_to_legacy_u32(&eee->supported,
           keee->supported);
 ethtool_convert_link_mode_to_legacy_u32(&eee->advertised,
      keee->advertised);
 ethtool_convert_link_mode_to_legacy_u32(&eee->lp_advertised,
      keee->lp_advertised);
 if (overflow)
  pr_warn("Ethtool ioctl interface doesn't support passing EEE linkmodes beyond bit 32\n");
}

static int ethtool_get_eee(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_keee keee;
 struct ethtool_eee eee;
 int rc;

 if (!dev->ethtool_ops->get_eee)
  return -EOPNOTSUPP;

 memset(&keee, 0, sizeof(keee));
 rc = dev->ethtool_ops->get_eee(dev, &keee);
 if (rc)
  return rc;

 keee_to_eee(&eee, &keee);
 if (copy_to_user(useraddr, &eee, sizeof(eee)))
  return -EFAULT;

 return 0;
}

static int ethtool_set_eee(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_keee keee;
 struct ethtool_eee eee;
 int ret;

 if (!dev->ethtool_ops->set_eee)
  return -EOPNOTSUPP;

 if (copy_from_user(&eee, useraddr, sizeof(eee)))
  return -EFAULT;

 eee_to_keee(&keee, &eee);
 ret = dev->ethtool_ops->set_eee(dev, &keee);
 if (!ret)
  ethtool_notify(dev, ETHTOOL_MSG_EEE_NTF);
 return ret;
}

static int ethtool_nway_reset(struct net_device *dev)
{
 if (!dev->ethtool_ops->nway_reset)
  return -EOPNOTSUPP;

 return dev->ethtool_ops->nway_reset(dev);
}

static int ethtool_get_link(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_value edata = { .cmd = ETHTOOL_GLINK };
 int link = __ethtool_get_link(dev);

 if (link < 0)
  return link;

 edata.data = link;
 if (copy_to_user(useraddr, &edata, sizeof(edata)))
  return -EFAULT;
 return 0;
}

static int ethtool_get_any_eeprom(struct net_device *dev, void __user *useraddr,
      int (*getter)(struct net_device *,
      struct ethtool_eeprom *, u8 *),
      u32 total_len)
{
 struct ethtool_eeprom eeprom;
 void __user *userbuf = useraddr + sizeof(eeprom);
 u32 bytes_remaining;
 u8 *data;
 int ret = 0;

 if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
  return -EFAULT;

 /* Check for wrap and zero */
 if (eeprom.offset + eeprom.len <= eeprom.offset)
  return -EINVAL;

 /* Check for exceeding total eeprom len */
 if (eeprom.offset + eeprom.len > total_len)
  return -EINVAL;

 data = kzalloc(PAGE_SIZE, GFP_USER);
 if (!data)
  return -ENOMEM;

 bytes_remaining = eeprom.len;
 while (bytes_remaining > 0) {
  eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE);

  ret = getter(dev, &eeprom, data);
  if (ret)
   break;
  if (!eeprom.len) {
   ret = -EIO;
   break;
  }
  if (copy_to_user(userbuf, data, eeprom.len)) {
   ret = -EFAULT;
   break;
  }
  userbuf += eeprom.len;
  eeprom.offset += eeprom.len;
  bytes_remaining -= eeprom.len;
 }

 eeprom.len = userbuf - (useraddr + sizeof(eeprom));
 eeprom.offset -= eeprom.len;
 if (copy_to_user(useraddr, &eeprom, sizeof(eeprom)))
  ret = -EFAULT;

 kfree(data);
 return ret;
}

static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
{
 const struct ethtool_ops *ops = dev->ethtool_ops;

 if (!ops->get_eeprom || !ops->get_eeprom_len ||
     !ops->get_eeprom_len(dev))
  return -EOPNOTSUPP;

 return ethtool_get_any_eeprom(dev, useraddr, ops->get_eeprom,
          ops->get_eeprom_len(dev));
}

static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_eeprom eeprom;
 const struct ethtool_ops *ops = dev->ethtool_ops;
 void __user *userbuf = useraddr + sizeof(eeprom);
 u32 bytes_remaining;
 u8 *data;
 int ret = 0;

 if (!ops->set_eeprom || !ops->get_eeprom_len ||
     !ops->get_eeprom_len(dev))
  return -EOPNOTSUPP;

 if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
  return -EFAULT;

 /* Check for wrap and zero */
 if (eeprom.offset + eeprom.len <= eeprom.offset)
  return -EINVAL;

 /* Check for exceeding total eeprom len */
 if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
  return -EINVAL;

 data = kzalloc(PAGE_SIZE, GFP_USER);
 if (!data)
  return -ENOMEM;

 bytes_remaining = eeprom.len;
 while (bytes_remaining > 0) {
  eeprom.len = min(bytes_remaining, (u32)PAGE_SIZE);

  if (copy_from_user(data, userbuf, eeprom.len)) {
   ret = -EFAULT;
   break;
  }
  ret = ops->set_eeprom(dev, &eeprom, data);
  if (ret)
   break;
  userbuf += eeprom.len;
  eeprom.offset += eeprom.len;
  bytes_remaining -= eeprom.len;
 }

 kfree(data);
 return ret;
}

static noinline_for_stack int ethtool_get_coalesce(struct net_device *dev,
         void __user *useraddr)
{
 struct ethtool_coalesce coalesce = { .cmd = ETHTOOL_GCOALESCE };
 struct kernel_ethtool_coalesce kernel_coalesce = {};
 int ret;

 if (!dev->ethtool_ops->get_coalesce)
  return -EOPNOTSUPP;

 ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce,
          NULL);
 if (ret)
  return ret;

 if (copy_to_user(useraddr, &coalesce, sizeof(coalesce)))
  return -EFAULT;
 return 0;
}

static bool
ethtool_set_coalesce_supported(struct net_device *dev,
          struct ethtool_coalesce *coalesce)
{
 u32 supported_params = dev->ethtool_ops->supported_coalesce_params;
 u32 nonzero_params = 0;

 if (coalesce->rx_coalesce_usecs)
  nonzero_params |= ETHTOOL_COALESCE_RX_USECS;
 if (coalesce->rx_max_coalesced_frames)
  nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES;
 if (coalesce->rx_coalesce_usecs_irq)
  nonzero_params |= ETHTOOL_COALESCE_RX_USECS_IRQ;
 if (coalesce->rx_max_coalesced_frames_irq)
  nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_IRQ;
 if (coalesce->tx_coalesce_usecs)
  nonzero_params |= ETHTOOL_COALESCE_TX_USECS;
 if (coalesce->tx_max_coalesced_frames)
  nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES;
 if (coalesce->tx_coalesce_usecs_irq)
  nonzero_params |= ETHTOOL_COALESCE_TX_USECS_IRQ;
 if (coalesce->tx_max_coalesced_frames_irq)
  nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ;
 if (coalesce->stats_block_coalesce_usecs)
  nonzero_params |= ETHTOOL_COALESCE_STATS_BLOCK_USECS;
 if (coalesce->use_adaptive_rx_coalesce)
  nonzero_params |= ETHTOOL_COALESCE_USE_ADAPTIVE_RX;
 if (coalesce->use_adaptive_tx_coalesce)
  nonzero_params |= ETHTOOL_COALESCE_USE_ADAPTIVE_TX;
 if (coalesce->pkt_rate_low)
  nonzero_params |= ETHTOOL_COALESCE_PKT_RATE_LOW;
 if (coalesce->rx_coalesce_usecs_low)
  nonzero_params |= ETHTOOL_COALESCE_RX_USECS_LOW;
 if (coalesce->rx_max_coalesced_frames_low)
  nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW;
 if (coalesce->tx_coalesce_usecs_low)
  nonzero_params |= ETHTOOL_COALESCE_TX_USECS_LOW;
 if (coalesce->tx_max_coalesced_frames_low)
  nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_LOW;
 if (coalesce->pkt_rate_high)
  nonzero_params |= ETHTOOL_COALESCE_PKT_RATE_HIGH;
 if (coalesce->rx_coalesce_usecs_high)
  nonzero_params |= ETHTOOL_COALESCE_RX_USECS_HIGH;
 if (coalesce->rx_max_coalesced_frames_high)
  nonzero_params |= ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH;
 if (coalesce->tx_coalesce_usecs_high)
  nonzero_params |= ETHTOOL_COALESCE_TX_USECS_HIGH;
 if (coalesce->tx_max_coalesced_frames_high)
  nonzero_params |= ETHTOOL_COALESCE_TX_MAX_FRAMES_HIGH;
 if (coalesce->rate_sample_interval)
  nonzero_params |= ETHTOOL_COALESCE_RATE_SAMPLE_INTERVAL;

 return (supported_params & nonzero_params) == nonzero_params;
}

static noinline_for_stack int ethtool_set_coalesce(struct net_device *dev,
         void __user *useraddr)
{
 struct kernel_ethtool_coalesce kernel_coalesce = {};
 struct ethtool_coalesce coalesce;
 int ret;

 if (!dev->ethtool_ops->set_coalesce || !dev->ethtool_ops->get_coalesce)
  return -EOPNOTSUPP;

 ret = dev->ethtool_ops->get_coalesce(dev, &coalesce, &kernel_coalesce,
          NULL);
 if (ret)
  return ret;

 if (copy_from_user(&coalesce, useraddr, sizeof(coalesce)))
  return -EFAULT;

 if (!ethtool_set_coalesce_supported(dev, &coalesce))
  return -EOPNOTSUPP;

 ret = dev->ethtool_ops->set_coalesce(dev, &coalesce, &kernel_coalesce,
          NULL);
 if (!ret)
  ethtool_notify(dev, ETHTOOL_MSG_COALESCE_NTF);
 return ret;
}

static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_ringparam ringparam = { .cmd = ETHTOOL_GRINGPARAM };
 struct kernel_ethtool_ringparam kernel_ringparam = {};

 if (!dev->ethtool_ops->get_ringparam)
  return -EOPNOTSUPP;

 dev->ethtool_ops->get_ringparam(dev, &ringparam,
     &kernel_ringparam, NULL);

 if (copy_to_user(useraddr, &ringparam, sizeof(ringparam)))
  return -EFAULT;
 return 0;
}

static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
{
 struct kernel_ethtool_ringparam kernel_ringparam;
 struct ethtool_ringparam ringparam, max;
 int ret;

 if (!dev->ethtool_ops->set_ringparam || !dev->ethtool_ops->get_ringparam)
  return -EOPNOTSUPP;

 if (copy_from_user(&ringparam, useraddr, sizeof(ringparam)))
  return -EFAULT;

 ethtool_ringparam_get_cfg(dev, &max, &kernel_ringparam, NULL);

 /* ensure new ring parameters are within the maximums */
 if (ringparam.rx_pending > max.rx_max_pending ||
     ringparam.rx_mini_pending > max.rx_mini_max_pending ||
     ringparam.rx_jumbo_pending > max.rx_jumbo_max_pending ||
     ringparam.tx_pending > max.tx_max_pending)
  return -EINVAL;

 ret = dev->ethtool_ops->set_ringparam(dev, &ringparam,
           &kernel_ringparam, NULL);
 if (!ret)
  ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF);
 return ret;
}

static noinline_for_stack int ethtool_get_channels(struct net_device *dev,
         void __user *useraddr)
{
 struct ethtool_channels channels = { .cmd = ETHTOOL_GCHANNELS };

 if (!dev->ethtool_ops->get_channels)
  return -EOPNOTSUPP;

 dev->ethtool_ops->get_channels(dev, &channels);

 if (copy_to_user(useraddr, &channels, sizeof(channels)))
  return -EFAULT;
 return 0;
}

static noinline_for_stack int ethtool_set_channels(struct net_device *dev,
         void __user *useraddr)
{
 struct ethtool_channels channels, curr = { .cmd = ETHTOOL_GCHANNELS };
 u16 from_channel, to_channel;
 unsigned int i;
 int ret;

 if (!dev->ethtool_ops->set_channels || !dev->ethtool_ops->get_channels)
  return -EOPNOTSUPP;

 if (copy_from_user(&channels, useraddr, sizeof(channels)))
  return -EFAULT;

 dev->ethtool_ops->get_channels(dev, &curr);

 if (channels.rx_count == curr.rx_count &&
     channels.tx_count == curr.tx_count &&
     channels.combined_count == curr.combined_count &&
     channels.other_count == curr.other_count)
  return 0;

 /* ensure new counts are within the maximums */
 if (channels.rx_count > curr.max_rx ||
     channels.tx_count > curr.max_tx ||
     channels.combined_count > curr.max_combined ||
     channels.other_count > curr.max_other)
  return -EINVAL;

 /* ensure there is at least one RX and one TX channel */
 if (!channels.combined_count &&
     (!channels.rx_count || !channels.tx_count))
  return -EINVAL;

 ret = ethtool_check_max_channel(dev, channels, NULL);
 if (ret)
  return ret;

 /* Disabling channels, query zero-copy AF_XDP sockets */
 from_channel = channels.combined_count +
  min(channels.rx_count, channels.tx_count);
 to_channel = curr.combined_count + max(curr.rx_count, curr.tx_count);
 for (i = from_channel; i < to_channel; i++)
  if (xsk_get_pool_from_qid(dev, i))
   return -EINVAL;

 ret = dev->ethtool_ops->set_channels(dev, &channels);
 if (!ret)
  ethtool_notify(dev, ETHTOOL_MSG_CHANNELS_NTF);
 return ret;
}

static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_pauseparam pauseparam = { .cmd = ETHTOOL_GPAUSEPARAM };

 if (!dev->ethtool_ops->get_pauseparam)
  return -EOPNOTSUPP;

 dev->ethtool_ops->get_pauseparam(dev, &pauseparam);

 if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam)))
  return -EFAULT;
 return 0;
}

static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_pauseparam pauseparam;
 int ret;

 if (!dev->ethtool_ops->set_pauseparam)
  return -EOPNOTSUPP;

 if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
  return -EFAULT;

 ret = dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
 if (!ret)
  ethtool_notify(dev, ETHTOOL_MSG_PAUSE_NTF);
 return ret;
}

static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
{
 struct ethtool_test test;
 const struct ethtool_ops *ops = dev->ethtool_ops;
 u64 *data;
 int ret, test_len;

 if (!ops->self_test || !ops->get_sset_count)
  return -EOPNOTSUPP;

 test_len = ops->get_sset_count(dev, ETH_SS_TEST);
 if (test_len < 0)
  return test_len;
 WARN_ON(test_len == 0);

 if (copy_from_user(&test, useraddr, sizeof(test)))
  return -EFAULT;

 test.len = test_len;
 data = kcalloc(test_len, sizeof(u64), GFP_USER);
 if (!data)
  return -ENOMEM;

 netif_testing_on(dev);
 ops->self_test(dev, &test, data);
 netif_testing_off(dev);

 ret = -EFAULT;
 if (copy_to_user(useraddr, &test, sizeof(test)))
  goto out;
 useraddr += sizeof(test);
 if (copy_to_user(useraddr, data, array_size(test.len, sizeof(u64))))
  goto out;
 ret = 0;

 out:
 kfree(data);
 return ret;
}

static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_gstrings gstrings;
 u8 *data;
 int ret;

 if (copy_from_user(&gstrings, useraddr, sizeof(gstrings)))
  return -EFAULT;

 ret = __ethtool_get_sset_count(dev, gstrings.string_set);
 if (ret < 0)
  return ret;
 if (ret > S32_MAX / ETH_GSTRING_LEN)
  return -ENOMEM;
 WARN_ON_ONCE(!ret);

 gstrings.len = ret;

 if (gstrings.len) {
  data = vzalloc(array_size(gstrings.len, ETH_GSTRING_LEN));
  if (!data)
   return -ENOMEM;

  __ethtool_get_strings(dev, gstrings.string_set, data);
 } else {
  data = NULL;
 }

 ret = -EFAULT;
 if (copy_to_user(useraddr, &gstrings, sizeof(gstrings)))
  goto out;
 useraddr += sizeof(gstrings);
 if (gstrings.len &&
     copy_to_user(useraddr, data,
    array_size(gstrings.len, ETH_GSTRING_LEN)))
  goto out;
 ret = 0;

out:
 vfree(data);
 return ret;
}

__printf(2, 3) void ethtool_sprintf(u8 **data, const char *fmt, ...)
{
 va_list args;

 va_start(args, fmt);
 vsnprintf(*data, ETH_GSTRING_LEN, fmt, args);
 va_end(args);

 *data += ETH_GSTRING_LEN;
}
EXPORT_SYMBOL(ethtool_sprintf);

void ethtool_puts(u8 **data, const char *str)
{
 strscpy(*data, str, ETH_GSTRING_LEN);
 *data += ETH_GSTRING_LEN;
}
EXPORT_SYMBOL(ethtool_puts);

static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_value id;
 static bool busy;
 const struct ethtool_ops *ops = dev->ethtool_ops;
 netdevice_tracker dev_tracker;
 int rc;

 if (!ops->set_phys_id)
  return -EOPNOTSUPP;

 if (busy)
  return -EBUSY;

 if (copy_from_user(&id, useraddr, sizeof(id)))
  return -EFAULT;

 rc = ops->set_phys_id(dev, ETHTOOL_ID_ACTIVE);
 if (rc < 0)
  return rc;

 /* Drop the RTNL lock while waiting, but prevent reentry or
 * removal of the device.
 */

 busy = true;
 netdev_hold(dev, &dev_tracker, GFP_KERNEL);
 netdev_unlock_ops(dev);
 rtnl_unlock();

 if (rc == 0) {
  /* Driver will handle this itself */
  schedule_timeout_interruptible(
   id.data ? (id.data * HZ) : MAX_SCHEDULE_TIMEOUT);
 } else {
  /* Driver expects to be called at twice the frequency in rc */
  int n = rc * 2, interval = HZ / n;
  u64 count = mul_u32_u32(n, id.data);
  u64 i = 0;

  do {
   rtnl_lock();
   netdev_lock_ops(dev);
   rc = ops->set_phys_id(dev,
        (i++ & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON);
   netdev_unlock_ops(dev);
   rtnl_unlock();
   if (rc)
    break;
   schedule_timeout_interruptible(interval);
  } while (!signal_pending(current) && (!id.data || i < count));
 }

 rtnl_lock();
 netdev_lock_ops(dev);
 netdev_put(dev, &dev_tracker);
 busy = false;

 (void) ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE);
 return rc;
}

static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
{
 struct ethtool_stats stats;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.37 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge