| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * DPAA2 Ethernet Switch ethtool support | |
| * | |
| * Copyright 2014-2016 Freescale Semiconductor Inc. | |
| * Copyright 2017-2018, 2024-2026 NXP | |
| * | |
| */ | |
| #include <linux/ethtool.h> | |
| #include "dpaa2-switch.h" | |
| static struct { | |
| enum dpsw_counter id; | |
| char name[ETH_GSTRING_LEN]; | |
| } dpaa2_switch_ethtool_counters[] = { | |
| {DPSW_CNT_ING_FRAME, "[hw] rx frames"}, | |
| {DPSW_CNT_ING_BYTE, "[hw] rx bytes"}, | |
| {DPSW_CNT_ING_FLTR_FRAME, "[hw] rx filtered frames"}, | |
| {DPSW_CNT_ING_FRAME_DISCARD, "[hw] rx discarded frames"}, | |
| {DPSW_CNT_ING_BCAST_FRAME, "[hw] rx bcast frames"}, | |
| {DPSW_CNT_ING_BCAST_BYTES, "[hw] rx bcast bytes"}, | |
| {DPSW_CNT_ING_MCAST_FRAME, "[hw] rx mcast frames"}, | |
| {DPSW_CNT_ING_MCAST_BYTE, "[hw] rx mcast bytes"}, | |
| {DPSW_CNT_EGR_FRAME, "[hw] tx frames"}, | |
| {DPSW_CNT_EGR_BYTE, "[hw] tx bytes"}, | |
| {DPSW_CNT_EGR_FRAME_DISCARD, "[hw] tx discarded frames"}, | |
| {DPSW_CNT_ING_NO_BUFF_DISCARD, "[hw] rx nobuffer discards"}, | |
| }; | |
| #define DPAA2_SWITCH_NUM_COUNTERS ARRAY_SIZE(dpaa2_switch_ethtool_counters) | |
| static void dpaa2_switch_get_drvinfo(struct net_device *netdev, | |
| struct ethtool_drvinfo *drvinfo) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(netdev); | |
| u16 version_major, version_minor; | |
| int err; | |
| strscpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); | |
| err = dpsw_get_api_version(port_priv->ethsw_data->mc_io, 0, | |
| &version_major, | |
| &version_minor); | |
| if (err) | |
| strscpy(drvinfo->fw_version, "N/A", | |
| sizeof(drvinfo->fw_version)); | |
| else | |
| snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), | |
| "%u.%u", version_major, version_minor); | |
| strscpy(drvinfo->bus_info, dev_name(netdev->dev.parent->parent), | |
| sizeof(drvinfo->bus_info)); | |
| } | |
| static int | |
| dpaa2_switch_get_link_ksettings(struct net_device *netdev, | |
| struct ethtool_link_ksettings *link_ksettings) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(netdev); | |
| struct dpsw_link_state state = {0}; | |
| int err; | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_is_type_phy(port_priv)) { | |
| err = phylink_ethtool_ksettings_get(port_priv->mac->phylink, | |
| link_ksettings); | |
| mutex_unlock(&port_priv->mac_lock); | |
| return err; | |
| } | |
| mutex_unlock(&port_priv->mac_lock); | |
| err = dpsw_if_get_link_state(port_priv->ethsw_data->mc_io, 0, | |
| port_priv->ethsw_data->dpsw_handle, | |
| port_priv->idx, | |
| &state); | |
| if (err) { | |
| netdev_err(netdev, "ERROR %d getting link state\n", err); | |
| goto out; | |
| } | |
| /* At the moment, we have no way of interrogating the DPMAC | |
| * from the DPSW side or there may not exist a DPMAC at all. | |
| * Report only autoneg state, duplexity and speed. | |
| */ | |
| if (state.options & DPSW_LINK_OPT_AUTONEG) | |
| link_ksettings->base.autoneg = AUTONEG_ENABLE; | |
| if (!(state.options & DPSW_LINK_OPT_HALF_DUPLEX)) | |
| link_ksettings->base.duplex = DUPLEX_FULL; | |
| link_ksettings->base.speed = state.rate; | |
| out: | |
| return err; | |
| } | |
| static int | |
| dpaa2_switch_set_link_ksettings(struct net_device *netdev, | |
| const struct ethtool_link_ksettings *link_ksettings) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(netdev); | |
| struct ethsw_core *ethsw = port_priv->ethsw_data; | |
| struct dpsw_link_cfg cfg = {0}; | |
| bool if_running; | |
| int err = 0, ret; | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_is_type_phy(port_priv)) { | |
| err = phylink_ethtool_ksettings_set(port_priv->mac->phylink, | |
| link_ksettings); | |
| mutex_unlock(&port_priv->mac_lock); | |
| return err; | |
| } | |
| mutex_unlock(&port_priv->mac_lock); | |
| /* Interface needs to be down to change link settings */ | |
| if_running = netif_running(netdev); | |
| if (if_running) { | |
| err = dpsw_if_disable(ethsw->mc_io, 0, | |
| ethsw->dpsw_handle, | |
| port_priv->idx); | |
| if (err) { | |
| netdev_err(netdev, "dpsw_if_disable err %d\n", err); | |
| return err; | |
| } | |
| } | |
| cfg.rate = link_ksettings->base.speed; | |
| if (link_ksettings->base.autoneg == AUTONEG_ENABLE) | |
| cfg.options |= DPSW_LINK_OPT_AUTONEG; | |
| else | |
| cfg.options &= ~DPSW_LINK_OPT_AUTONEG; | |
| if (link_ksettings->base.duplex == DUPLEX_HALF) | |
| cfg.options |= DPSW_LINK_OPT_HALF_DUPLEX; | |
| else | |
| cfg.options &= ~DPSW_LINK_OPT_HALF_DUPLEX; | |
| err = dpsw_if_set_link_cfg(port_priv->ethsw_data->mc_io, 0, | |
| port_priv->ethsw_data->dpsw_handle, | |
| port_priv->idx, | |
| &cfg); | |
| if (if_running) { | |
| ret = dpsw_if_enable(ethsw->mc_io, 0, | |
| ethsw->dpsw_handle, | |
| port_priv->idx); | |
| if (ret) { | |
| netdev_err(netdev, "dpsw_if_enable err %d\n", ret); | |
| return ret; | |
| } | |
| } | |
| return err; | |
| } | |
| static int | |
| dpaa2_switch_ethtool_get_sset_count(struct net_device *netdev, int sset) | |
| { | |
| switch (sset) { | |
| case ETH_SS_STATS: | |
| return DPAA2_SWITCH_NUM_COUNTERS + dpaa2_mac_get_sset_count(); | |
| default: | |
| return -EOPNOTSUPP; | |
| } | |
| } | |
| static void dpaa2_switch_ethtool_get_strings(struct net_device *netdev, | |
| u32 stringset, u8 *data) | |
| { | |
| const char *str; | |
| int i; | |
| switch (stringset) { | |
| case ETH_SS_STATS: | |
| for (i = 0; i < DPAA2_SWITCH_NUM_COUNTERS; i++) { | |
| str = dpaa2_switch_ethtool_counters[i].name; | |
| ethtool_puts(&data, str); | |
| } | |
| dpaa2_mac_get_strings(&data); | |
| break; | |
| } | |
| } | |
| static void dpaa2_switch_ethtool_get_stats(struct net_device *netdev, | |
| struct ethtool_stats *stats, | |
| u64 *data) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(netdev); | |
| int i, err; | |
| for (i = 0; i < DPAA2_SWITCH_NUM_COUNTERS; i++) { | |
| err = dpsw_if_get_counter(port_priv->ethsw_data->mc_io, 0, | |
| port_priv->ethsw_data->dpsw_handle, | |
| port_priv->idx, | |
| dpaa2_switch_ethtool_counters[i].id, | |
| &data[i]); | |
| if (err) | |
| netdev_err(netdev, "dpsw_if_get_counter[%s] err %d\n", | |
| dpaa2_switch_ethtool_counters[i].name, err); | |
| } | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_has_mac(port_priv)) | |
| dpaa2_mac_get_ethtool_stats(port_priv->mac, data + i); | |
| mutex_unlock(&port_priv->mac_lock); | |
| } | |
| static void | |
| dpaa2_switch_get_rmon_stats(struct net_device *netdev, | |
| struct ethtool_rmon_stats *rmon_stats, | |
| const struct ethtool_rmon_hist_range **ranges) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(netdev); | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_has_mac(port_priv)) | |
| dpaa2_mac_get_rmon_stats(port_priv->mac, rmon_stats, ranges); | |
| mutex_unlock(&port_priv->mac_lock); | |
| } | |
| static void | |
| dpaa2_switch_get_ctrl_stats(struct net_device *net_dev, | |
| struct ethtool_eth_ctrl_stats *ctrl_stats) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(net_dev); | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_has_mac(port_priv)) | |
| dpaa2_mac_get_ctrl_stats(port_priv->mac, ctrl_stats); | |
| mutex_unlock(&port_priv->mac_lock); | |
| } | |
| static void | |
| dpaa2_switch_get_eth_mac_stats(struct net_device *net_dev, | |
| struct ethtool_eth_mac_stats *eth_mac_stats) | |
| { | |
| struct ethsw_port_priv *port_priv = netdev_priv(net_dev); | |
| mutex_lock(&port_priv->mac_lock); | |
| if (dpaa2_switch_port_has_mac(port_priv)) | |
| dpaa2_mac_get_eth_mac_stats(port_priv->mac, eth_mac_stats); | |
| mutex_unlock(&port_priv->mac_lock); | |
| } | |
| const struct ethtool_ops dpaa2_switch_port_ethtool_ops = { | |
| .get_drvinfo = dpaa2_switch_get_drvinfo, | |
| .get_link = ethtool_op_get_link, | |
| .get_link_ksettings = dpaa2_switch_get_link_ksettings, | |
| .set_link_ksettings = dpaa2_switch_set_link_ksettings, | |
| .get_strings = dpaa2_switch_ethtool_get_strings, | |
| .get_ethtool_stats = dpaa2_switch_ethtool_get_stats, | |
| .get_sset_count = dpaa2_switch_ethtool_get_sset_count, | |
| .get_rmon_stats = dpaa2_switch_get_rmon_stats, | |
| .get_eth_ctrl_stats = dpaa2_switch_get_ctrl_stats, | |
| .get_eth_mac_stats = dpaa2_switch_get_eth_mac_stats, | |
| }; |