| // 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, |
| }; |