mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-06-21 15:43:21 +02:00
f9a3e05114
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>
275 lines
7.4 KiB
C
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);
|
|
}
|