Files
linux-stable-mirror/drivers/net/netdevsim/ethtool.c
T
Jakub Kicinski f9a3e05114 net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops
Make ethtool not take rtnl_lock for SET commands when operation
is performed on an ops-locked driver. cfg/cfg_pending are now
ops-locked, since only ethtool modifies them.

Some SET driver callbacks will still need rtnl_lock, most notably
those which may end up calling netdev_update_features() or the qdisc
layer (via netif_set_real_num_tx_queues()). Let drivers selectively
opt back into the rtnl_lock with a new bitfield in ops.

We need two helpers since Netlink and ioctl cmds have different
values. Keep the helpers side by side in common.h to make sure
they get updated together, even tho they will only get called
from ioctl.c and netlink.c.

SET commands which don't use ethnl_default_set_doit() are converted
by subsequent commits.

Reviewed-by: Eric Dumazet <edumazet@google.com>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Link: https://patch.msgid.link/20260605002912.3456868-6-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-06-09 10:13:05 -07:00

275 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2020 Facebook
#include <linux/debugfs.h>
#include <linux/random.h>
#include <net/netdev_queues.h>
#include "netdevsim.h"
static void
nsim_get_pause_stats(struct net_device *dev,
struct ethtool_pause_stats *pause_stats)
{
struct netdevsim *ns = netdev_priv(dev);
if (ns->ethtool.pauseparam.report_stats_rx)
pause_stats->rx_pause_frames = 1;
if (ns->ethtool.pauseparam.report_stats_tx)
pause_stats->tx_pause_frames = 2;
}
static void
nsim_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
struct netdevsim *ns = netdev_priv(dev);
pause->autoneg = 0; /* We don't support ksettings, so can't pretend */
pause->rx_pause = ns->ethtool.pauseparam.rx;
pause->tx_pause = ns->ethtool.pauseparam.tx;
}
static int
nsim_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause)
{
struct netdevsim *ns = netdev_priv(dev);
if (pause->autoneg)
return -EINVAL;
ns->ethtool.pauseparam.rx = pause->rx_pause;
ns->ethtool.pauseparam.tx = pause->tx_pause;
return 0;
}
static int nsim_get_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(coal, &ns->ethtool.coalesce, sizeof(ns->ethtool.coalesce));
return 0;
}
static int nsim_set_coalesce(struct net_device *dev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(&ns->ethtool.coalesce, coal, sizeof(ns->ethtool.coalesce));
return 0;
}
static void nsim_get_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
memcpy(ring, &ns->ethtool.ring, sizeof(ns->ethtool.ring));
kernel_ring->hds_thresh_max = NSIM_HDS_THRESHOLD_MAX;
if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_UNKNOWN)
kernel_ring->tcp_data_split = ETHTOOL_TCP_DATA_SPLIT_ENABLED;
}
static int nsim_set_ringparam(struct net_device *dev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct netdevsim *ns = netdev_priv(dev);
ns->ethtool.ring.rx_pending = ring->rx_pending;
ns->ethtool.ring.rx_jumbo_pending = ring->rx_jumbo_pending;
ns->ethtool.ring.rx_mini_pending = ring->rx_mini_pending;
ns->ethtool.ring.tx_pending = ring->tx_pending;
return 0;
}
static void
nsim_get_channels(struct net_device *dev, struct ethtool_channels *ch)
{
struct netdevsim *ns = netdev_priv(dev);
ch->max_combined = ns->nsim_bus_dev->num_queues;
ch->combined_count = ns->ethtool.channels;
}
static void
nsim_wake_queues(struct net_device *dev)
{
struct netdevsim *ns = netdev_priv(dev);
struct netdevsim *peer;
synchronize_net();
netif_tx_wake_all_queues(dev);
rcu_read_lock();
peer = rcu_dereference(ns->peer);
if (peer)
netif_tx_wake_all_queues(peer->netdev);
rcu_read_unlock();
}
static int
nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch)
{
struct netdevsim *ns = netdev_priv(dev);
int err;
err = netif_set_real_num_queues(dev, ch->combined_count,
ch->combined_count);
if (err)
return err;
ns->ethtool.channels = ch->combined_count;
/* Only wake up queues if devices are linked */
if (rcu_access_pointer(ns->peer))
nsim_wake_queues(dev);
return 0;
}
static int
nsim_get_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
{
struct netdevsim *ns = netdev_priv(dev);
if (ns->ethtool.get_err)
return -ns->ethtool.get_err;
memcpy(fecparam, &ns->ethtool.fec, sizeof(ns->ethtool.fec));
return 0;
}
static int
nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam)
{
struct netdevsim *ns = netdev_priv(dev);
u32 fec;
if (ns->ethtool.set_err)
return -ns->ethtool.set_err;
memcpy(&ns->ethtool.fec, fecparam, sizeof(ns->ethtool.fec));
fec = fecparam->fec;
if (fec == ETHTOOL_FEC_AUTO)
fec |= ETHTOOL_FEC_OFF;
fec |= ETHTOOL_FEC_NONE;
ns->ethtool.fec.active_fec = 1 << (fls(fec) - 1);
return 0;
}
static const struct ethtool_fec_hist_range netdevsim_fec_ranges[] = {
{ 0, 0},
{ 1, 3},
{ 4, 7},
{ 0, 0}
};
static void
nsim_get_fec_stats(struct net_device *dev, struct ethtool_fec_stats *fec_stats,
struct ethtool_fec_hist *hist)
{
struct ethtool_fec_hist_value *values = hist->values;
hist->ranges = netdevsim_fec_ranges;
fec_stats->corrected_blocks.total = 123;
fec_stats->uncorrectable_blocks.total = 4;
values[0].per_lane[0] = 125;
values[0].per_lane[1] = 120;
values[0].per_lane[2] = 100;
values[0].per_lane[3] = 100;
values[1].sum = 12;
values[2].sum = 2;
values[2].per_lane[0] = 2;
values[2].per_lane[1] = 0;
values[2].per_lane[2] = 0;
values[2].per_lane[3] = 0;
}
static int nsim_get_ts_info(struct net_device *dev,
struct kernel_ethtool_ts_info *info)
{
struct netdevsim *ns = netdev_priv(dev);
info->phc_index = mock_phc_index(ns->phc);
return 0;
}
static const struct ethtool_ops nsim_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS,
.supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
ETHTOOL_RING_USE_HDS_THRS,
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS,
.get_pause_stats = nsim_get_pause_stats,
.get_pauseparam = nsim_get_pauseparam,
.set_pauseparam = nsim_set_pauseparam,
.set_coalesce = nsim_set_coalesce,
.get_coalesce = nsim_get_coalesce,
.get_ringparam = nsim_get_ringparam,
.set_ringparam = nsim_set_ringparam,
.get_channels = nsim_get_channels,
.set_channels = nsim_set_channels,
.get_fecparam = nsim_get_fecparam,
.set_fecparam = nsim_set_fecparam,
.get_fec_stats = nsim_get_fec_stats,
.get_ts_info = nsim_get_ts_info,
};
static void nsim_ethtool_ring_init(struct netdevsim *ns)
{
ns->ethtool.ring.rx_pending = 512;
ns->ethtool.ring.rx_max_pending = 4096;
ns->ethtool.ring.rx_jumbo_max_pending = 4096;
ns->ethtool.ring.rx_mini_max_pending = 4096;
ns->ethtool.ring.tx_pending = 512;
ns->ethtool.ring.tx_max_pending = 4096;
}
void nsim_ethtool_init(struct netdevsim *ns)
{
struct dentry *ethtool, *dir;
ns->netdev->ethtool_ops = &nsim_ethtool_ops;
nsim_ethtool_ring_init(ns);
ns->ethtool.pauseparam.report_stats_rx = true;
ns->ethtool.pauseparam.report_stats_tx = true;
ns->ethtool.fec.fec = ETHTOOL_FEC_NONE;
ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE;
ns->ethtool.channels = ns->nsim_bus_dev->num_queues;
ethtool = debugfs_create_dir("ethtool", ns->nsim_dev_port->ddir);
debugfs_create_u32("get_err", 0600, ethtool, &ns->ethtool.get_err);
debugfs_create_u32("set_err", 0600, ethtool, &ns->ethtool.set_err);
dir = debugfs_create_dir("pause", ethtool);
debugfs_create_bool("report_stats_rx", 0600, dir,
&ns->ethtool.pauseparam.report_stats_rx);
debugfs_create_bool("report_stats_tx", 0600, dir,
&ns->ethtool.pauseparam.report_stats_tx);
dir = debugfs_create_dir("ring", ethtool);
debugfs_create_u32("rx_max_pending", 0600, dir,
&ns->ethtool.ring.rx_max_pending);
debugfs_create_u32("rx_jumbo_max_pending", 0600, dir,
&ns->ethtool.ring.rx_jumbo_max_pending);
debugfs_create_u32("rx_mini_max_pending", 0600, dir,
&ns->ethtool.ring.rx_mini_max_pending);
debugfs_create_u32("tx_max_pending", 0600, dir,
&ns->ethtool.ring.tx_max_pending);
}