linux/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c

222 lines
6 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* DPAA2 Ethernet Switch ethtool support
*
* Copyright 2014-2016 Freescale Semiconductor Inc.
* Copyright 2017-2018 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:
net: dpaa2: publish MAC stringset to ethtool -S even if MAC is missing DPNIs and DPSW objects can connect and disconnect at runtime from DPMAC objects on the same fsl-mc bus. The DPMAC object also holds "ethtool -S" unstructured counters. Those counters are only shown for the entity owning the netdev (DPNI, DPSW) if it's connected to a DPMAC. The ethtool stringset code path is split into multiple callbacks, but currently, connecting and disconnecting the DPMAC takes the rtnl_lock(). This blocks the entire ethtool code path from running, see ethnl_default_doit() -> rtnl_lock() -> ops->prepare_data() -> strset_prepare_data(). This is going to be a problem if we are going to no longer require rtnl_lock() when connecting/disconnecting the DPMAC, because the DPMAC could appear between ops->get_sset_count() and ops->get_strings(). If it appears out of the blue, we will provide a stringset into an array that was dimensioned thinking the DPMAC wouldn't be there => array accessed out of bounds. There isn't really a good way to work around that, and I don't want to put too much pressure on the ethtool framework by playing locking games. Just make the DPMAC counters be always available. They'll be zeroes if the DPNI or DPSW isn't connected to a DPMAC. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Reviewed-by: Ioana Ciornei <ioana.ciornei@nxp.com> Tested-by: Ioana Ciornei <ioana.ciornei@nxp.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
2022-11-29 16:12:16 +02:00
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);
}
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,
};