1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
/* Ethtool support for Mellanox Gigabit Ethernet driver
*
* Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES
*/
#include <linux/phy.h>
#include "mlxbf_gige.h"
#include "mlxbf_gige_regs.h"
/* Start of struct ethtool_ops functions */
static int mlxbf_gige_get_regs_len(struct net_device *netdev)
{
return MLXBF_GIGE_MMIO_REG_SZ;
}
static void mlxbf_gige_get_regs(struct net_device *netdev,
struct ethtool_regs *regs, void *p)
{
struct mlxbf_gige *priv = netdev_priv(netdev);
regs->version = MLXBF_GIGE_REGS_VERSION;
/* Read entire MMIO register space and store results
* into the provided buffer. By design, a read to an
* offset without an existing register will be
* acknowledged and return zero.
*/
memcpy_fromio(p, priv->base, MLXBF_GIGE_MMIO_REG_SZ);
}
static void
mlxbf_gige_get_ringparam(struct net_device *netdev,
struct ethtool_ringparam *ering,
struct kernel_ethtool_ringparam *kernel_ering,
struct netlink_ext_ack *extack)
{
struct mlxbf_gige *priv = netdev_priv(netdev);
ering->rx_max_pending = MLXBF_GIGE_MAX_RXQ_SZ;
ering->tx_max_pending = MLXBF_GIGE_MAX_TXQ_SZ;
ering->rx_pending = priv->rx_q_entries;
ering->tx_pending = priv->tx_q_entries;
}
static const struct {
const char string[ETH_GSTRING_LEN];
} mlxbf_gige_ethtool_stats_keys[] = {
{ "hw_access_errors" },
{ "tx_invalid_checksums" },
{ "tx_small_frames" },
{ "tx_index_errors" },
{ "sw_config_errors" },
{ "sw_access_errors" },
{ "rx_truncate_errors" },
{ "rx_mac_errors" },
{ "rx_din_dropped_pkts" },
{ "tx_fifo_full" },
{ "rx_filter_passed_pkts" },
{ "rx_filter_discard_pkts" },
};
static int mlxbf_gige_get_sset_count(struct net_device *netdev, int stringset)
{
if (stringset != ETH_SS_STATS)
return -EOPNOTSUPP;
return ARRAY_SIZE(mlxbf_gige_ethtool_stats_keys);
}
static void mlxbf_gige_get_strings(struct net_device *netdev, u32 stringset,
u8 *buf)
{
if (stringset != ETH_SS_STATS)
return;
memcpy(buf, &mlxbf_gige_ethtool_stats_keys,
sizeof(mlxbf_gige_ethtool_stats_keys));
}
static void mlxbf_gige_get_ethtool_stats(struct net_device *netdev,
struct ethtool_stats *estats,
u64 *data)
{
struct mlxbf_gige *priv = netdev_priv(netdev);
/* Fill data array with interface statistics
*
* NOTE: the data writes must be in
* sync with the strings shown in
* the mlxbf_gige_ethtool_stats_keys[] array
*
* NOTE2: certain statistics below are zeroed upon
* port disable, so the calculation below
* must include the "cached" value of the stat
* plus the value read directly from hardware.
* Cached statistics are currently:
* rx_din_dropped_pkts
* rx_filter_passed_pkts
* rx_filter_discard_pkts
*/
*data++ = priv->stats.hw_access_errors;
*data++ = priv->stats.tx_invalid_checksums;
*data++ = priv->stats.tx_small_frames;
*data++ = priv->stats.tx_index_errors;
*data++ = priv->stats.sw_config_errors;
*data++ = priv->stats.sw_access_errors;
*data++ = priv->stats.rx_truncate_errors;
*data++ = priv->stats.rx_mac_errors;
*data++ = (priv->stats.rx_din_dropped_pkts +
readq(priv->base + MLXBF_GIGE_RX_DIN_DROP_COUNTER));
*data++ = priv->stats.tx_fifo_full;
*data++ = (priv->stats.rx_filter_passed_pkts +
readq(priv->base + MLXBF_GIGE_RX_PASS_COUNTER_ALL));
*data++ = (priv->stats.rx_filter_discard_pkts +
readq(priv->base + MLXBF_GIGE_RX_DISC_COUNTER_ALL));
}
static void mlxbf_gige_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
pause->autoneg = AUTONEG_DISABLE;
pause->rx_pause = 1;
pause->tx_pause = 1;
}
static bool mlxbf_gige_llu_counters_enabled(struct mlxbf_gige *priv)
{
u32 data;
if (priv->hw_version == MLXBF_GIGE_VERSION_BF2) {
data = readl(priv->llu_base + MLXBF_GIGE_BF2_LLU_GENERAL_CONFIG);
if (data & MLXBF_GIGE_BF2_LLU_COUNTERS_EN)
return true;
} else {
data = readl(priv->llu_base + MLXBF_GIGE_BF3_LLU_GENERAL_CONFIG);
if (data & MLXBF_GIGE_BF3_LLU_COUNTERS_EN)
return true;
}
return false;
}
static void mlxbf_gige_get_pause_stats(struct net_device *netdev,
struct ethtool_pause_stats *pause_stats)
{
struct mlxbf_gige *priv = netdev_priv(netdev);
u64 data_lo, data_hi;
/* Read LLU counters to provide stats only if counters are enabled */
if (mlxbf_gige_llu_counters_enabled(priv)) {
data_lo = readl(priv->llu_base + MLXBF_GIGE_TX_PAUSE_CNT_LO);
data_hi = readl(priv->llu_base + MLXBF_GIGE_TX_PAUSE_CNT_HI);
pause_stats->tx_pause_frames = (data_hi << 32) | data_lo;
data_lo = readl(priv->llu_base + MLXBF_GIGE_RX_PAUSE_CNT_LO);
data_hi = readl(priv->llu_base + MLXBF_GIGE_RX_PAUSE_CNT_HI);
pause_stats->rx_pause_frames = (data_hi << 32) | data_lo;
}
}
const struct ethtool_ops mlxbf_gige_ethtool_ops = {
.get_link = ethtool_op_get_link,
.get_ringparam = mlxbf_gige_get_ringparam,
.get_regs_len = mlxbf_gige_get_regs_len,
.get_regs = mlxbf_gige_get_regs,
.get_strings = mlxbf_gige_get_strings,
.get_sset_count = mlxbf_gige_get_sset_count,
.get_ethtool_stats = mlxbf_gige_get_ethtool_stats,
.nway_reset = phy_ethtool_nway_reset,
.get_pauseparam = mlxbf_gige_get_pauseparam,
.get_pause_stats = mlxbf_gige_get_pause_stats,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
};
|