| // 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); | |
| ns->ethtool_ddir = ethtool; | |
| 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); | |
| } | |
| void nsim_ethtool_fini(struct netdevsim *ns) | |
| { | |
| debugfs_remove(ns->ethtool_ddir); | |
| } |